diff --git a/.github/workflows/appflowy_editor_test.yml b/.github/workflows/appflowy_editor_test.yml index c007259bf8..229ef85efd 100644 --- a/.github/workflows/appflowy_editor_test.yml +++ b/.github/workflows/appflowy_editor_test.yml @@ -4,10 +4,12 @@ on: push: branches: - "main" + - "release/*" pull_request: branches: - "main" + - "release/*" paths: - "frontend/app_flowy/packages/appflowy_editor/**" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e3ca2c3a76..dca1ecb2ec 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,10 +4,12 @@ on: push: branches: - "main" + - "release/*" pull_request: branches: - "main" + - "release/*" jobs: build: @@ -62,6 +64,8 @@ jobs: sudo apt-get update sudo apt-get install -y dart curl build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev sudo apt-get install keybinder-3.0 + elif [ "$RUNNER_OS" == "Windows" ]; then + vcpkg integrate install elif [ "$RUNNER_OS" == "macOS" ]; then echo 'do nothing' fi @@ -85,7 +89,7 @@ jobs: flutter config --enable-linux-desktop elif [ "$RUNNER_OS" == "macOS" ]; then flutter config --enable-macos-desktop - elif [ "$RUNNER_OS" == "windows" ]; then + elif [ "$RUNNER_OS" == "Windows" ]; then flutter config --enable-windows-desktop fi shell: bash diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index 6ce8630b8d..678677e3d4 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -9,12 +9,14 @@ on: push: branches: - "main" + - "release/*" paths: - "frontend/app_flowy/**" pull_request: branches: - "main" + - "release/*" paths: - "frontend/app_flowy/**" @@ -73,7 +75,7 @@ jobs: run: | cargo make --profile development-linux-x86_64 flowy-sdk-dev - - name: Code Generation + - name: Flutter Code Generation working-directory: frontend/app_flowy run: | flutter packages pub run easy_localization:generate -f keys -o locale_keys.g.dart -S assets/translations -s en.json diff --git a/.github/workflows/dart_test.yml b/.github/workflows/dart_test.yml index 17694e3fec..39a938628e 100644 --- a/.github/workflows/dart_test.yml +++ b/.github/workflows/dart_test.yml @@ -1,15 +1,17 @@ -name: Unit test(Flutter) +name: Frontend test on: push: branches: - "main" + - "release/*" paths: - "frontend/app_flowy/**" pull_request: branches: - "main" + - "release/*" paths: - "frontend/app_flowy/**" @@ -21,7 +23,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 with: toolchain: "stable-2022-04-07" @@ -53,10 +54,6 @@ jobs: working-directory: frontend run: | cargo install cargo-make - - - name: Cargo make flowy dev - working-directory: frontend - run: | cargo make flowy_dev - name: Flutter Deps @@ -64,12 +61,12 @@ jobs: run: | flutter config --enable-linux-desktop - - name: Build FlowySDK + - name: Build Test lib working-directory: frontend run: | - cargo make --profile test-linux test-lib-build + cargo make --profile test-linux build-test-lib - - name: Code Generation + - name: Flutter Code Generation working-directory: frontend/app_flowy run: | flutter packages pub get diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7e22b4b46..03f2bb13db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} + release_name: v${{ github.ref }} body_path: ${{ env.RELEASE_NOTES_PATH }} build-linux-x86: @@ -37,6 +37,7 @@ jobs: env: LINUX_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/linux/Release LINUX_ZIP_NAME: AppFlowy-linux-x86.tar.gz + LINUX_PACKAGE_NAME: AppFlowy_${{ github.ref_name }}_linux-amd64.deb steps: - name: Checkout uses: actions/checkout@v2 @@ -70,6 +71,46 @@ jobs: flutter config --enable-linux-desktop cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-linux-x86_64 appflowy + - name: Build Linux package + working-directory: ${{ env.LINUX_APP_RELEASE_PATH }} + run: | + mkdir -p package/opt && mv AppFlowy package/opt/ + cd package && mkdir DEBIAN + # Create control file + printf 'Package: AppFlowy + Version: %s + Architecture: amd64 + Essential: no + Priority: optional + Maintainer: AppFlowy + Description: An Open Source Alternative to Notion\n' "${{ github.ref_name }}" > DEBIAN/control + + # postinst script for creating symlink + printf '#!/bin/bash + if [ -e /usr/local/bin/appflowy ]; then + echo "Symlink already exists, skipping." + else + echo "Creating Symlink in /usr/local/bin/appflowy" + ln -s /opt/AppFlowy/app_flowy /usr/local/bin/appflowy + fi' > DEBIAN/postinst + chmod 0755 DEBIAN/postinst + + # postrm script for cleaning up residuals + printf '#!/bin/bash + if [ -e /usr/local/bin/appflowy ]; then + rm /usr/local/bin/appflowy + fi' > DEBIAN/postrm + chmod 0755 DEBIAN/postrm + + mkdir -p usr/share/applications + # Update Exec & icon path in desktop entry + grep -rl "\[CHANGE_THIS\]" ./opt/AppFlowy/appflowy.desktop.temp | xargs sed -i "s/\[CHANGE_THIS\]/\/opt/" + # Add desktop entry in package + mv ./opt/AppFlowy/appflowy.desktop.temp ./usr/share/applications/appflowy.desktop + + # Build + cd ../ && dpkg-deb --build --root-owner-group package ${{ env.LINUX_PACKAGE_NAME }} + - name: Upload Release Asset id: upload-release-asset uses: actions/upload-release-asset@v1 @@ -81,12 +122,24 @@ jobs: asset_name: ${{ env.LINUX_ZIP_NAME }} asset_content_type: application/octet-stream + - name: Upload Release Asset Install Package + id: upload-release-asset-install-package + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: ${{ env.LINUX_APP_RELEASE_PATH }}/${{ env.LINUX_PACKAGE_NAME }} + asset_name: ${{ env.LINUX_PACKAGE_NAME }} + asset_content_type: application/octet-stream + build-macos-x86_64: runs-on: macos-latest needs: create-release env: MACOS_APP_RELEASE_PATH: frontend/app_flowy/product/${{ github.ref_name }}/macos/Release MACOS_X86_ZIP_NAME: Appflowy-macos-x86_64.zip + MACOS_DMG_NAME: Appflowy-macos-x86_64-installer steps: - name: Checkout uses: actions/checkout@v2 @@ -116,6 +169,21 @@ jobs: flutter config --enable-macos-desktop cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy + - name: Create MacOS dmg + working-directory: frontend + run: | + brew install create-dmg + create-dmg \ + --volname ${{ env.MACOS_DMG_NAME }} \ + --hide-extension "AppFlowy.app" \ + --background scripts/dmg_assets/AppFlowyInstallerBackground.jpg \ + --window-size 600 450 \ + --icon-size 94 \ + --icon "AppFlowy.app" 141 249 \ + --app-drop-link 458 249 \ + "${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \ + "${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app" + - name: Archive macOS app working-directory: ${{ env.MACOS_APP_RELEASE_PATH }} run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app @@ -129,6 +197,15 @@ jobs: asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }} asset_name: ${{ env.MACOS_X86_ZIP_NAME }} asset_content_type: application/octet-stream + - name: Upload DMG Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create-release.outputs.upload_url }} + asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg + asset_name: ${{ env.MACOS_DMG_NAME }}.dmg + asset_content_type: application/octet-stream build-windows-x86_64: runs-on: windows-latest diff --git a/.github/workflows/rust_coverage.yml b/.github/workflows/rust_coverage.yml index b63d0c2202..512796cb93 100644 --- a/.github/workflows/rust_coverage.yml +++ b/.github/workflows/rust_coverage.yml @@ -4,6 +4,7 @@ on: push: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" @@ -11,6 +12,7 @@ on: pull_request: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" diff --git a/.github/workflows/rust_lint.yml b/.github/workflows/rust_lint.yml index 2221ad02a0..4ea8ba40a2 100644 --- a/.github/workflows/rust_lint.yml +++ b/.github/workflows/rust_lint.yml @@ -4,6 +4,7 @@ on: push: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" @@ -11,6 +12,7 @@ on: pull_request: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" diff --git a/.github/workflows/rust_test.yml b/.github/workflows/rust_test.yml index 3a42f9074f..3f49d5085b 100644 --- a/.github/workflows/rust_test.yml +++ b/.github/workflows/rust_test.yml @@ -1,9 +1,10 @@ -name: Unit test(Rust) +name: Backend test on: push: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" @@ -11,6 +12,7 @@ on: pull_request: branches: - "main" + - "release/*" paths: - "frontend/rust-lib/**" - "shared-lib/**" diff --git a/CHANGELOG.md b/CHANGELOG.md index 954e319b52..7fcb348030 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,24 @@ # Release Notes +## Version 0.0.6.2 - 10/30/2022 +- Fix some bugs + +## Version 0.0.6.1 - 10/26/2022 +### New features +- Optimzie appflowy_editor dark mode style + +### Bug Fixes +- Unable to copy the text with checkbox or link style + +## Version 0.0.6 - 10/23/2022 + +### New features +- Integrate **appflowy_editor** + + ## Version 0.0.5.3 - 09/26/2022 -New features +### New features - Open the next page automatically after deleting the current page - Refresh the Kanban board after altering a property type @@ -16,7 +32,7 @@ New features ## Version 0.0.5.2 - 09/16/2022 -New features +### New features - Enable adding a new card to the "No Status" group - Fix some bugs @@ -27,19 +43,19 @@ New features ## Version 0.0.5.1 - 09/14/2022 -New features +### New features - Enable deleting a field in board - Fix some bugs ## Version 0.0.5 - 09/08/2022 -New Features - Kanban Board like Notion and Trello beta +### New features - Kanban Board like Notion and Trello beta Boards are the best way to manage projects & tasks. Use them to group your databases by select, multiselect, and checkbox.

-- Set up columns that represent a specific phase of the project cycle and use cards to represent each project / task -- Drag and drop a card from one phase / column to another phase / column +- Set up columns that represent a specific phase of the project cycle and use cards to represent each project/task +- Drag and drop a card from one phase/column to another phase/column - Update database properties in the Board view by clicking on a property and making edits on the card ### Other Features & Improvements @@ -49,7 +65,7 @@ Boards are the best way to manage projects & tasks. Use them to group your datab ## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022 -New features +### New features - Board-view database - Support start editing after creating a new card - Support editing the card directly by clicking the edit button @@ -61,7 +77,7 @@ New features ## Version 0.0.5 - beta.1 - 08/25/2022 -New features +### New features - Board-view database - Group by single select - drag and drop cards @@ -84,7 +100,7 @@ New features ## Version 0.0.4 - beta.3 - 05/02/2022 - Drag to reorder app/ view/ field -- Row record open as a page +- Row record opens as a page - Auto resize the height of the row in the grid - Support more number formats - Search column options, supporting Single-select, Multi-select, and number format @@ -108,7 +124,7 @@ New features ## Version 0.0.4 - beta.1 - 04/08/2022 v0.0.4 - beta.1 is pre-release -New features +### New features - Table-view database - supported column types: Text, Checkbox, Single-select, Multi-select, Numbers - hide / delete columns @@ -117,12 +133,12 @@ New features ## Version 0.0.3 - 02/23/2022 v0.0.3 is production ready, available on Linux, macOS, and Windows -New features +### New features - Dark Mode - Support new languages: French, Italian, Russian, Simplified Chinese, Spanish - Add Settings: Toggle on Dark Mode; Select a language - Show device info -- Add tooltip on toolbar icons +- Add tooltip on the toolbar icons Bug fixes and improvements - Increased height of action diff --git a/README.md b/README.md index 2a53db9b24..93d6a11c70 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,15 @@ You are in charge of your data and customizations. Twitter

-

The Open Source Alternative To Notion.

+

The Open Source Alternative To Notion.

The Open Source Alternative To Notion.

The Open Source Alternative To Notion.

## User Installation -Please view the [documentation](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods) for OS specific installation instructions. +* [Windows/Mac/Linux](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/mac-windows-linux-packages) +* [Docker](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/installing-with-docker) +* [Source](https://appflowy.gitbook.io/docs/essential-documentation/install-appflowy/installation-methods/from-source) ## Built With @@ -49,7 +51,9 @@ Please view the [documentation](https://appflowy.gitbook.io/docs/essential-docum - [AppFlowy Roadmap ReadMe](https://appflowy.gitbook.io/docs/essential-documentation/roadmap) - [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12) -If you'd like to propose a feature, submit an issue [here](https://github.com/AppFlowy-IO/appflowy/issues). + +If you'd like to propose a feature, submit a feature request [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=feature_request.yaml&title=%5BFR%5D+)
+If you'd like to report a bug, submit bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+) ## **Releases** @@ -91,7 +95,7 @@ To be honest, we do not claim to outperform Notion in terms of functionality and ## License -Distributed under the AGPLv3 License. See `LICENSE.md` for more information. +Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information. ## Acknowledgements diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index 332a8e72db..92a2ce040e 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi" LIB_NAME = "dart_ffi" -CURRENT_APP_VERSION = "0.0.5.2" +CURRENT_APP_VERSION = "0.0.6.2" FEATURES = "flutter" PRODUCT_NAME = "AppFlowy" # CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html @@ -47,6 +47,7 @@ PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cach # Test default config TEST_CRATE_TYPE = "cdylib" TEST_LIB_EXT = "dylib" +TEST_RUST_LOG = "info" TEST_BUILD_FLAG = "debug" TEST_COMPILE_TARGET = "x86_64-apple-darwin" diff --git a/frontend/app_flowy/android/README.md b/frontend/app_flowy/android/README.md index a073fde807..52f0dd3e6f 100644 --- a/frontend/app_flowy/android/README.md +++ b/frontend/app_flowy/android/README.md @@ -12,8 +12,8 @@ When compiling for android we need the following pre-requisites: - [Download](https://developer.android.com/ndk/downloads/) Android NDK version 24. - When downloading Android NDK you can get the compressed version as a standalone from the site. Or you can download it through [Android Studio](https://developer.android.com/studio). -- After downloading the two you need to set the environment variables. For Windows that's a seperate process. - On MacOs and Linux the process is similar. +- After downloading the two you need to set the environment variables. For Windows that's a separate process. + On macOS and Linux the process is similar. - The variables needed are '$ANDROID_NDK_HOME', this will point to where the NDK is located. --- @@ -48,9 +48,9 @@ linker = "/home/user/Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux **Folder path: 'Android/Sdk/ndk/24.0.8215888/toolchains/llvm/prebuilt/linux-x86_64/lib64/clang/14.0.1/lib/linux'.** After that you have to copy this file into three different folders namely aarch64, arm, i386 and x86_64. -We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldnt have to do this process. -Though using NDK v22 will not give us alot of features to work with. -This github [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this. +We have to do this so we Android NDK can find clang on our system, if we used NDK 22 we wouldn't have to do this process. +Though using NDK v22 will not give us a lot of features to work with. +This GitHub [issue](https://github.com/fzyzcjy/flutter_rust_bridge/issues/419) explains the reason why we are doing this. --- diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/OFL.txt b/frontend/app_flowy/assets/google_fonts/Poppins/OFL.txt new file mode 100644 index 0000000000..246c977c9f --- /dev/null +++ b/frontend/app_flowy/assets/google_fonts/Poppins/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Black.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Black.ttf new file mode 100644 index 0000000000..71c0f995ee Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Black.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BlackItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BlackItalic.ttf new file mode 100644 index 0000000000..7aeb58bd1b Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BlackItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Bold.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Bold.ttf new file mode 100644 index 0000000000..00559eeb29 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Bold.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BoldItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BoldItalic.ttf new file mode 100644 index 0000000000..e61e8e88bd Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-BoldItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBold.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBold.ttf new file mode 100644 index 0000000000..df7093608a Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBold.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf new file mode 100644 index 0000000000..14d2b375dc Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLight.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLight.ttf new file mode 100644 index 0000000000..e76ec69a65 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLight.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf new file mode 100644 index 0000000000..89513d9469 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Italic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Italic.ttf new file mode 100644 index 0000000000..12b7b3c40b Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Italic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Light.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Light.ttf new file mode 100644 index 0000000000..bc36bcc242 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Light.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-LightItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-LightItalic.ttf new file mode 100644 index 0000000000..9e70be6a9e Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-LightItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Medium.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Medium.ttf new file mode 100644 index 0000000000..6bcdcc27f2 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Medium.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-MediumItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-MediumItalic.ttf new file mode 100644 index 0000000000..be67410fd0 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-MediumItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Regular.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Regular.ttf new file mode 100644 index 0000000000..9f0c71b70a Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Regular.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBold.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBold.ttf new file mode 100644 index 0000000000..74c726e327 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBold.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf new file mode 100644 index 0000000000..3e6c942233 Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Thin.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Thin.ttf new file mode 100644 index 0000000000..03e736613a Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-Thin.ttf differ diff --git a/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ThinItalic.ttf b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ThinItalic.ttf new file mode 100644 index 0000000000..e26db5dd3d Binary files /dev/null and b/frontend/app_flowy/assets/google_fonts/Poppins/Poppins-ThinItalic.ttf differ diff --git a/frontend/app_flowy/assets/translations/en.json b/frontend/app_flowy/assets/translations/en.json index 2381d5b288..70ebe3245f 100644 --- a/frontend/app_flowy/assets/translations/en.json +++ b/frontend/app_flowy/assets/translations/en.json @@ -221,7 +221,7 @@ "menuName": "Grid" }, "document": { - "menuName": "Doc", + "menuName": "Document", "date": { "timeHintTextInTwelveHour": "01:00 PM", "timeHintTextInTwentyFourHour": "13:00" @@ -232,4 +232,4 @@ "create_new_card": "New" } } -} +} \ No newline at end of file diff --git a/frontend/app_flowy/assets/translations/fr-FR.json b/frontend/app_flowy/assets/translations/fr-FR.json index 2de9e63704..0900cafa2f 100644 --- a/frontend/app_flowy/assets/translations/fr-FR.json +++ b/frontend/app_flowy/assets/translations/fr-FR.json @@ -221,7 +221,7 @@ "menuName": "Grille" }, "document": { - "menuName": "Doc", + "menuName": "Document", "date": { "timeHintTextInTwelveHour": "01:00 PM", "timeHintTextInTwentyFourHour": "13:00" @@ -232,4 +232,4 @@ "create_new_card": "Nouveau" } } -} +} \ No newline at end of file diff --git a/frontend/app_flowy/assets/translations/id-ID.json b/frontend/app_flowy/assets/translations/id-ID.json index 1239194266..38f4e7d805 100644 --- a/frontend/app_flowy/assets/translations/id-ID.json +++ b/frontend/app_flowy/assets/translations/id-ID.json @@ -209,7 +209,7 @@ "menuName": "Grid" }, "document": { - "menuName": "Doc", + "menuName": "Dokter", "date": { "timeHintTextInTwelveHour": "01:00 PM", "timeHintTextInTwentyFourHour": "13:00" diff --git a/frontend/app_flowy/assets/translations/pt-BR.json b/frontend/app_flowy/assets/translations/pt-BR.json index 4bea9c6a3d..65e5879673 100644 --- a/frontend/app_flowy/assets/translations/pt-BR.json +++ b/frontend/app_flowy/assets/translations/pt-BR.json @@ -220,10 +220,10 @@ "menuName": "Grade" }, "document": { - "menuName": "Doc", + "menuName": "Documento", "date": { - "timeHintTextInTwelveHour": "12:00 AM", - "timeHintTextInTwentyFourHour": "12:00" + "timeHintTextInTwelveHour": "01:00 PM", + "timeHintTextInTwentyFourHour": "13:00" } }, "board": { diff --git a/frontend/app_flowy/assets/translations/sv.json b/frontend/app_flowy/assets/translations/sv.json new file mode 100644 index 0000000000..e6cf4c6ff2 --- /dev/null +++ b/frontend/app_flowy/assets/translations/sv.json @@ -0,0 +1,235 @@ +{ + "appName": "AppFlowy", + "defaultUsername": "Jag", + "welcomeText": "Välkommen till @:appName", + "githubStarText": "Stärnmärk på GitHub", + "subscribeNewsletterText": "Prenumerera på nyhetsbrev", + "letsGoButtonText": "Kör igång", + "title": "Namn", + "signUp": { + "buttonText": "Registrera dig", + "title": "Registrera dig på @:appName", + "getStartedText": "Sätt igång", + "emptyPasswordError": "Lösenordet kan inte vara tomt", + "repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt", + "unmatchedPasswordError": "Upprepat lösenord är inte samma som det första", + "alreadyHaveAnAccount": "Har du redan ett konto?", + "emailHint": "E-post", + "passwordHint": "Lösenord", + "repeatPasswordHint": "Uprepa lösenordet" + }, + "signIn": { + "loginTitle": "Logga in till @:appName", + "loginButtonText": "Logga in", + "buttonText": "Registrering", + "forgotPassword": "Glömt lösenordet?", + "emailHint": "E-post", + "passwordHint": "Lösenord", + "dontHaveAnAccount": "Har du inget konto?", + "repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt", + "unmatchedPasswordError": "Upprepat lösenord är inte samma som det första" + }, + "workspace": { + "create": "Skapa arbetsyta", + "hint": "Arbetsyta", + "notFoundError": "Hittade ingen arbetsyta" + }, + "shareAction": { + "buttonText": "Dela", + "workInProgress": "Kommer snart", + "markdown": "Markdown", + "copyLink": "Kopiera länk" + }, + "disclosureAction": { + "rename": "Byt namn", + "delete": "Ta bort", + "duplicate": "Klona" + }, + "blankPageTitle": "Tom sida", + "newPageText": "Ny sida", + "trash": { + "text": "Skräp", + "restoreAll": "Återställ alla", + "deleteAll": "Ta bort alla", + "pageHeader": { + "fileName": "Filnamn", + "lastModified": "Ändrad", + "created": "Skapad" + } + }, + "deletePagePrompt": { + "text": "Denna sida är i skräpmappen", + "restore": "Återställ sida", + "deletePermanent": "Radera permanent" + }, + "dialogCreatePageNameHint": "Sidnamn", + "questionBubble": { + "whatsNew": "Vad nytt?", + "help": "Hjälp & Support", + "debug": { + "name": "Felsökningsinfo", + "success": "Kopierade felsökningsinfo till urklipp!", + "fail": "Kunde inte kopiera felsökningsinfo till urklipp" + } + }, + "menuAppHeader": { + "addPageTooltip": "Lägg snabbt till en sida inuti", + "defaultNewPageName": "Namnlös", + "renameDialog": "Byt namn" + }, + "toolbar": { + "undo": "Ångra", + "redo": "Upprepa", + "bold": "Fet", + "italic": "Kursiv", + "underline": "Understruken", + "strike": "Genomstruken", + "numList": "Numrerad lista", + "bulletList": "Punktlista", + "checkList": "Checklista", + "inlineCode": "Infogad kod", + "quote": "Citatblock", + "header": "Rubrik", + "highlight": "Färgmarkera" + }, + "tooltip": { + "lightMode": "Växla till ljust läge", + "darkMode": "Växla till mörkt läge", + "openAsPage": "Öppna som sida", + "addNewRow": "Lägg till ny rad", + "openMenu": "Klicka för att öppna meny" + }, + "sideBar": { + "closeSidebar": "Stäng sidofältet", + "openSidebar": "Öppna sidofältet" + }, + "notifications": { + "export": { + "markdown": "Exporterade anteckning till Markdown", + "path": "Dokument/flowy" + } + }, + "contactsPage": { + "title": "Kontakter", + "whatsHappening": "Vad händer denna vecka?", + "addContact": "Lägg till kontakt", + "editContact": "Redigera kontakt" + }, + "button": { + "OK": "OK", + "Cancel": "Avbryt", + "signIn": "Logga in", + "signOut": "Logga ut", + "complete": "Slutfört", + "save": "Spara" + }, + "label": { + "welcome": "Välkommen!", + "firstName": "Förnamn", + "middleName": "Mellannamn", + "lastName": "Efternamn", + "stepX": "Steg {X}" + }, + "oAuth": { + "err": { + "failedTitle": "Kan inte ansluta till ditt konto.", + "failedMsg": "Tillse att du har slutfört registreringsprocessen i din webbläsare." + }, + "google": { + "title": "GOOGLE-inloggning", + "instruction1": "För att kunna importera dina Google-kontakter, måste du auktorisera detta program med hjälp av din webbläsare.", + "instruction2": "Kopiera den här koden till urklipp genom att klicka på ikonen eller genom att markera texten:", + "instruction3": "Gå till följande länk i din webbläsare, och ange ovanstående kod:", + "instruction4": "Tryck på nedanstående knapp när du slutfört registreringen:" + } + }, + "settings": { + "title": "Inställningar", + "menu": { + "appearance": "Utseende", + "language": "Språk", + "user": "Användare", + "open": "Öppna inställningarna" + }, + "appearance": { + "lightLabel": "Ljust läge", + "darkLabel": "Mörkt läge" + } + }, + "grid": { + "settings": { + "filter": "Filter", + "sortBy": "Sortera efter", + "Properties": "Egenskaper", + "group": "Grupp" + }, + "field": { + "hide": "Dölj", + "insertLeft": "Infoga till vänster", + "insertRight": "Infoga till höger", + "duplicate": "Klona", + "delete": "Ta bort", + "textFieldName": "Text", + "checkboxFieldName": "Checkruta", + "dateFieldName": "Datum", + "numberFieldName": "Siffror", + "singleSelectFieldName": "Välj", + "multiSelectFieldName": "Välj flera", + "urlFieldName": "URL", + "numberFormat": " Sifferformat", + "dateFormat": " Datumformat", + "includeTime": " Inkludera tid", + "dateFormatFriendly": "Månad Dag,År", + "dateFormatISO": "År-Månad-Dag", + "dateFormatLocal": "Månad/Dag/År", + "dateFormatUS": "År/Månad/Dag", + "timeFormat": " Tidsformat", + "invalidTimeFormat": "Ogiltigt format", + "timeFormatTwelveHour": "12-timmars", + "timeFormatTwentyFourHour": "24-timmars", + "addSelectOption": "Lägg till ett alternativ", + "optionTitle": "Alternativ", + "addOption": "Lägg till alternativ", + "editProperty": "Redigera egenskap", + "newColumn": "Ny kolumn", + "deleteFieldPromptMessage": "Är du säker? Denna egenskap kommer att raderas." + }, + "row": { + "duplicate": "Klona", + "delete": "Ta bort", + "textPlaceholder": "Tom", + "copyProperty": "Kopierade egenskap till urklipp", + "count": "Antal", + "newRow": "Ny rad" + }, + "selectOption": { + "create": "Skapa", + "purpleColor": "Purpur", + "pinkColor": "Rosa", + "lightPinkColor": "Ljusrosa", + "orangeColor": "Orange", + "yellowColor": "Gul", + "limeColor": "Lime", + "greenColor": "Grön", + "aquaColor": "Vatten", + "blueColor": "Blå", + "deleteTag": "Ta bort tagg", + "colorPanelTitle": "Färger", + "panelTitle": "Välj ett alternativ eller skapa ett", + "searchOption": "Sök efter ett alternativ" + }, + "menuName": "Tabell" + }, + "document": { + "menuName": "Dokument", + "date": { + "timeHintTextInTwelveHour": "01:00 PM", + "timeHintTextInTwentyFourHour": "13:00" + } + }, + "board": { + "column": { + "create_new_card": "Nytt" + } + } +} diff --git a/frontend/app_flowy/lib/core/grid_notification.dart b/frontend/app_flowy/lib/core/grid_notification.dart index 9a2429a417..7b177d4a65 100644 --- a/frontend/app_flowy/lib/core/grid_notification.dart +++ b/frontend/app_flowy/lib/core/grid_notification.dart @@ -9,31 +9,38 @@ import 'package:flowy_sdk/rust_stream.dart'; import 'notification_helper.dart'; // GridPB -typedef GridNotificationCallback = void Function(GridNotification, Either); +typedef GridNotificationCallback = void Function( + GridDartNotification, Either); -class GridNotificationParser extends NotificationParser { - GridNotificationParser({String? id, required GridNotificationCallback callback}) +class GridNotificationParser + extends NotificationParser { + GridNotificationParser( + {String? id, required GridNotificationCallback callback}) : super( id: id, callback: callback, - tyParser: (ty) => GridNotification.valueOf(ty), + tyParser: (ty) => GridDartNotification.valueOf(ty), errorParser: (bytes) => FlowyError.fromBuffer(bytes), ); } -typedef GridNotificationHandler = Function(GridNotification ty, Either result); +typedef GridNotificationHandler = Function( + GridDartNotification ty, Either result); class GridNotificationListener { StreamSubscription? _subscription; GridNotificationParser? _parser; - GridNotificationListener({required String objectId, required GridNotificationHandler handler}) + GridNotificationListener( + {required String objectId, required GridNotificationHandler handler}) : _parser = GridNotificationParser(id: objectId, callback: handler) { - _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable)); + _subscription = + RustStreamReceiver.listen((observable) => _parser?.parse(observable)); } Future stop() async { _parser = null; await _subscription?.cancel(); + _subscription = null; } } diff --git a/frontend/app_flowy/lib/core/network_monitor.dart b/frontend/app_flowy/lib/core/network_monitor.dart index ba7806873d..b1398e87fd 100644 --- a/frontend/app_flowy/lib/core/network_monitor.dart +++ b/frontend/app_flowy/lib/core/network_monitor.dart @@ -11,7 +11,8 @@ class NetworkListener { late StreamSubscription _connectivitySubscription; NetworkListener() { - _connectivitySubscription = _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); + _connectivitySubscription = + _connectivity.onConnectivityChanged.listen(_updateConnectionStatus); } Future start() async { @@ -39,9 +40,11 @@ class NetworkListener { return NetworkType.Ethernet; case ConnectivityResult.mobile: return NetworkType.Cell; - case ConnectivityResult.none: - return NetworkType.UnknownNetworkType; case ConnectivityResult.bluetooth: + return NetworkType.Bluetooth; + case ConnectivityResult.vpn: + return NetworkType.VPN; + case ConnectivityResult.none: return NetworkType.UnknownNetworkType; } }(); diff --git a/frontend/app_flowy/lib/plugins/blank/blank.dart b/frontend/app_flowy/lib/plugins/blank/blank.dart index 7595adefd5..65c3215131 100644 --- a/frontend/app_flowy/lib/plugins/blank/blank.dart +++ b/frontend/app_flowy/lib/plugins/blank/blank.dart @@ -15,6 +15,9 @@ class BlankPluginBuilder extends PluginBuilder { @override String get menuName => "Blank"; + @override + String get menuIcon => ""; + @override PluginType get pluginType => PluginType.blank; } diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index ce72413425..027fa94316 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -24,7 +24,8 @@ class BoardBloc extends Bloc { final BoardDataController _gridDataController; late final AppFlowyBoardController boardController; final MoveRowFFIService _rowService; - LinkedHashMap groupControllers = LinkedHashMap(); + final LinkedHashMap groupControllers = + LinkedHashMap(); GridFieldController get fieldController => _gridDataController.fieldController; @@ -69,7 +70,7 @@ class BoardBloc extends Bloc { await event.when( initial: () async { _startListening(); - await _loadGrid(emit); + await _openGrid(emit); }, createBottomRow: (groupId) async { final startRowId = groupControllers[groupId]?.lastRow()?.id; @@ -284,8 +285,8 @@ class BoardBloc extends Bloc { return [...items]; } - Future _loadGrid(Emitter emit) async { - final result = await _gridDataController.loadData(); + Future _openGrid(Emitter emit) async { + final result = await _gridDataController.openGrid(); result.fold( (grid) => emit( state.copyWith(loadingState: GridLoadingState.finish(left(unit))), diff --git a/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart b/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart index 641239e767..7ca035d375 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_data_controller.dart @@ -34,7 +34,8 @@ class BoardDataController { // key: the block id final LinkedHashMap _blocks; - LinkedHashMap get blocks => _blocks; + UnmodifiableMapView get blocks => + UnmodifiableMapView(_blocks); OnFieldsChanged? _onFieldsChanged; OnGridChanged? _onGridChanged; @@ -107,21 +108,22 @@ class BoardDataController { ); } - Future> loadData() async { - final result = await _gridFFIService.loadGrid(); + Future> openGrid() async { + final result = await _gridFFIService.openGrid(); return Future( () => result.fold( (grid) async { _onGridChanged?.call(grid); - return await fieldController.loadFields(fieldIds: grid.fields).then( - (result) => result.fold( - (l) { - _loadGroups(grid.blocks); - return left(l); - }, - (err) => right(err), - ), - ); + final result = await fieldController.loadFields( + fieldIds: grid.fields, + ); + return result.fold( + (l) { + _loadGroups(grid.blocks); + return left(l); + }, + (err) => right(err), + ); }, (err) => right(err), ), diff --git a/frontend/app_flowy/lib/plugins/board/application/board_listener.dart b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart index 9f8662fc5b..f8889ade86 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_listener.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_listener.dart @@ -32,18 +32,18 @@ class BoardListener { } void _handler( - GridNotification ty, + GridDartNotification ty, Either result, ) { switch (ty) { - case GridNotification.DidUpdateGroupView: + case GridDartNotification.DidUpdateGroupView: result.fold( (payload) => _groupUpdateNotifier?.value = left(GroupViewChangesetPB.fromBuffer(payload)), (error) => _groupUpdateNotifier?.value = right(error), ); break; - case GridNotification.DidGroupByNewField: + case GridDartNotification.DidGroupByNewField: result.fold( (payload) => _groupByNewFieldNotifier?.value = left(GroupViewChangesetPB.fromBuffer(payload).newGroups), diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart index c8a0d87f9c..8229fd1588 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_checkbox_cell_bloc.dart @@ -35,7 +35,7 @@ class BoardCheckboxCellBloc cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart index a19d7b64a8..0512fb8bb9 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_date_cell_bloc.dart @@ -31,7 +31,7 @@ class BoardDateCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart index 2cc4882357..4cf0930591 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_number_cell_bloc.dart @@ -32,7 +32,7 @@ class BoardNumberCellBloc cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart index daa4dcc383..e1c7195117 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_select_option_cell_bloc.dart @@ -34,7 +34,7 @@ class BoardSelectOptionCellBloc cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart index 9d1b14c605..7af1757329 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_text_cell_bloc.dart @@ -41,7 +41,7 @@ class BoardTextCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart index 045a1633fa..9494e7ae68 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/board_url_cell_bloc.dart @@ -38,7 +38,7 @@ class BoardURLCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart index 82372896d1..92461772e2 100644 --- a/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/card/card_bloc.dart @@ -66,6 +66,7 @@ class BoardCardBloc extends Bloc { state.cells.map((cell) => cell.identifier.fieldContext).toList(), ), rowPB: state.rowPB, + visible: true, ); } diff --git a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart index 6a148c0312..8db9fad4fd 100644 --- a/frontend/app_flowy/lib/plugins/board/application/group_controller.dart +++ b/frontend/app_flowy/lib/plugins/board/application/group_controller.dart @@ -39,7 +39,7 @@ class GroupController { void startListening() { _listener.start(onGroupChanged: (result) { result.fold( - (GroupChangesetPB changeset) { + (GroupRowsNotificationPB changeset) { for (final deletedRow in changeset.deletedRows) { group.rows.removeWhere((rowPB) => rowPB.id == deletedRow); delegate.removeRow(group, deletedRow); diff --git a/frontend/app_flowy/lib/plugins/board/application/group_listener.dart b/frontend/app_flowy/lib/plugins/board/application/group_listener.dart index e3b626af07..536cd35f88 100644 --- a/frontend/app_flowy/lib/plugins/board/application/group_listener.dart +++ b/frontend/app_flowy/lib/plugins/board/application/group_listener.dart @@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; -typedef UpdateGroupNotifiedValue = Either; +typedef UpdateGroupNotifiedValue = Either; class GroupListener { final GroupPB group; @@ -27,14 +27,14 @@ class GroupListener { } void _handler( - GridNotification ty, + GridDartNotification ty, Either result, ) { switch (ty) { - case GridNotification.DidUpdateGroup: + case GridDartNotification.DidUpdateGroup: result.fold( (payload) => _groupNotifier?.value = - left(GroupChangesetPB.fromBuffer(payload)), + left(GroupRowsNotificationPB.fromBuffer(payload)), (error) => _groupNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/plugins/board/board.dart b/frontend/app_flowy/lib/plugins/board/board.dart index e22677b9eb..2a709023cf 100644 --- a/frontend/app_flowy/lib/plugins/board/board.dart +++ b/frontend/app_flowy/lib/plugins/board/board.dart @@ -20,11 +20,14 @@ class BoardPluginBuilder implements PluginBuilder { @override String get menuName => "Board"; + @override + String get menuIcon => "editor/board"; + @override PluginType get pluginType => PluginType.board; @override - ViewDataTypePB get dataType => ViewDataTypePB.Database; + ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat; @override ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Board; diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index a7f0d90557..ac82d7de29 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -9,11 +9,9 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart'; -import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:appflowy_board/appflowy_board.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; @@ -22,7 +20,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import '../../grid/application/row/row_cache.dart'; import '../application/board_bloc.dart'; import 'card/card.dart'; @@ -102,27 +99,21 @@ class _BoardContentState extends State { } Widget _buildBoard(BuildContext context) { - return ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: Selector( - selector: (ctx, notifier) => notifier.theme, - builder: (ctx, theme, child) => Expanded( - child: AppFlowyBoard( - boardScrollController: scrollManager, - scrollController: ScrollController(), - controller: context.read().boardController, - headerBuilder: _buildHeader, - footerBuilder: _buildFooter, - cardBuilder: (_, column, columnItem) => _buildCard( - context, - column, - columnItem, - ), - groupConstraints: const BoxConstraints.tightFor(width: 300), - config: AppFlowyBoardConfig( - groupBackgroundColor: theme.bg1, - ), - ), + return Expanded( + child: AppFlowyBoard( + boardScrollController: scrollManager, + scrollController: ScrollController(), + controller: context.read().boardController, + headerBuilder: _buildHeader, + footerBuilder: _buildFooter, + cardBuilder: (_, column, columnItem) => _buildCard( + context, + column, + columnItem, + ), + groupConstraints: const BoxConstraints.tightFor(width: 300), + config: AppFlowyBoardConfig( + groupBackgroundColor: Theme.of(context).colorScheme.surfaceVariant, ), ), ); @@ -159,7 +150,6 @@ class _BoardContentState extends State { groupData.headerData.groupName, fontSize: 14, overflow: TextOverflow.clip, - color: context.read().textColor, ), ), icon: _buildHeaderIcon(boardCustomData), @@ -168,7 +158,7 @@ class _BoardContentState extends State { width: 20, child: svgWidget( "home/add", - color: context.read().iconColor, + color: Theme.of(context).colorScheme.onSurface, ), ), onAddButtonClick: () { @@ -191,13 +181,12 @@ class _BoardContentState extends State { width: 20, child: svgWidget( "home/add", - color: context.read().iconColor, + color: Theme.of(context).colorScheme.onSurface, ), ), title: FlowyText.medium( LocaleKeys.board_column_create_new_card.tr(), fontSize: 14, - color: context.read().textColor, ), height: 50, margin: config.footerPadding, @@ -276,10 +265,12 @@ class _BoardContentState extends State { } BoxDecoration _makeBoxDecoration(BuildContext context) { - final theme = context.read(); - final borderSide = BorderSide(color: theme.shader6, width: 1.0); + final borderSide = BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ); return BoxDecoration( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, border: Border.fromBorderSide(borderSide), borderRadius: const BorderRadius.all(Radius.circular(6)), ); @@ -296,6 +287,7 @@ class _BoardContentState extends State { gridId: gridId, fields: UnmodifiableListView(fieldController.fieldContexts), rowPB: rowPB, + visible: true, ); final dataController = GridRowDataController( @@ -329,15 +321,7 @@ class _ToolbarBlocAdaptor extends StatelessWidget { fieldController: bloc.fieldController, ); - return ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: Selector( - selector: (ctx, notifier) => notifier.theme, - builder: (ctx, theme, child) { - return BoardToolbar(toolbarContext: toolbarContext); - }, - ), - ); + return BoardToolbar(toolbarContext: toolbarContext); }, ); } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart index 18c0a84f47..4d62608c45 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_date_cell.dart @@ -1,6 +1,5 @@ import 'package:app_flowy/plugins/board/application/card/board_date_cell_bloc.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -53,7 +52,7 @@ class _BoardDateCellState extends State { child: FlowyText.regular( state.dateStr, fontSize: 13, - color: context.read().shader3, + color: Theme.of(context).hintColor, ), ), ); diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart index 3237316c13..f5252a178a 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_text_cell.dart @@ -1,9 +1,11 @@ import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'board_cell.dart'; import 'define.dart'; @@ -150,11 +152,7 @@ class _BoardTextCellState extends State { onChanged: (value) => focusChanged(), onEditingComplete: () => focusNode.unfocus(), maxLines: null, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - fontFamily: 'Mulish', - ), + style: Theme.of(context).textTheme.bodyMedium!.size(FontSizes.s14), decoration: InputDecoration( // Magic number 4 makes the textField take up the same space as FlowyText contentPadding: EdgeInsets.symmetric( diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart index f5d7dabf79..ab7ce3e84d 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/board_url_cell.dart @@ -1,8 +1,9 @@ import 'package:app_flowy/plugins/board/application/card/board_url_cell_bloc.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'define.dart'; @@ -34,7 +35,6 @@ class _BoardUrlCellState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocProvider.value( value: _cellBloc, child: BlocBuilder( @@ -53,11 +53,12 @@ class _BoardUrlCellState extends State { textAlign: TextAlign.left, text: TextSpan( text: state.content, - style: TextStyle( - color: theme.main2, - fontSize: 14, - decoration: TextDecoration.underline, - ), + style: Theme.of(context) + .textTheme + .bodyMedium! + .size(FontSizes.s14) + .textColor(Theme.of(context).colorScheme.primary) + .underline, ), ), ), diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart index bf1143523a..6dab2af136 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/card.dart @@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/board/application/card/card_data_controller.da import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -221,8 +220,10 @@ class _CardMoreOption extends StatelessWidget with CardAccessory { Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(3.0), - child: - svgWidget('grid/details', color: context.read().iconColor), + child: svgWidget( + 'grid/details', + color: Theme.of(context).colorScheme.onSurface, + ), ); } @@ -243,7 +244,7 @@ class _CardEditOption extends StatelessWidget with CardAccessory { padding: const EdgeInsets.all(3.0), child: svgWidget( 'editor/edit', - color: context.read().iconColor, + color: Theme.of(context).colorScheme.onSurface, ), ); } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart b/frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart index 0052e0e29c..9b7ce2c63b 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/card/container/accessory.dart @@ -1,7 +1,5 @@ -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; enum AccessoryType { edit, @@ -28,7 +26,6 @@ class CardAccessoryContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.read(); final children = accessories.map((accessory) { return GestureDetector( behavior: HitTestBehavior.opaque, @@ -36,17 +33,16 @@ class CardAccessoryContainer extends StatelessWidget { accessory.onTap(context); onTapAccessory(accessory.type); }, - child: _wrapHover(theme, accessory), + child: _wrapHover(context, accessory), ); }).toList(); return _wrapDecoration(context, Row(children: children)); } - FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) { + FlowyHover _wrapHover(BuildContext context, CardAccessory accessory) { return FlowyHover( style: HoverStyle( - hoverColor: theme.hover, - backgroundColor: theme.surface, + backgroundColor: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.zero, ), builder: (_, onHover) => SizedBox( @@ -58,8 +54,10 @@ class CardAccessoryContainer extends StatelessWidget { } Widget _wrapDecoration(BuildContext context, Widget child) { - final theme = context.read(); - final borderSide = BorderSide(color: theme.shader6, width: 1.0); + final borderSide = BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ); final decoration = BoxDecoration( color: Colors.transparent, border: Border.fromBorderSide(borderSide), diff --git a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart index b02cd0fc95..e583986edf 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_setting.dart @@ -7,7 +7,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_propert import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -95,7 +95,6 @@ class _SettingItem extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.read(); final isSelected = context .read() .state @@ -108,16 +107,17 @@ class _SettingItem extends StatelessWidget { isSelected: isSelected, text: FlowyText.medium( action.title(), - fontSize: 12, - color: theme.textColor, + fontSize: FontSizes.s12, ), - hoverColor: theme.hover, onTap: () { context .read() .add(BoardSettingEvent.performAction(action)); }, - leftIcon: svgWidget(action.iconName(), color: theme.iconColor), + leftIcon: svgWidget( + action.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart index f20dcd6aa0..fd00193f19 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/toolbar/board_toolbar.dart @@ -1,11 +1,9 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flutter/widgets.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter/material.dart'; import 'board_setting.dart'; @@ -61,16 +59,17 @@ class _SettingButtonState extends State<_SettingButton> { @override Widget build(BuildContext context) { - final theme = context.read(); return AppFlowyPopover( controller: popoverController, constraints: BoxConstraints.loose(const Size(260, 400)), child: FlowyIconButton( - hoverColor: theme.hover, width: 22, icon: Padding( padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0), - child: svgWidget("grid/setting/setting", color: theme.iconColor), + child: svgWidget( + "grid/setting/setting", + color: Theme.of(context).colorScheme.onSurface, + ), ), ), popupBuilder: (BuildContext popoverContext) { diff --git a/frontend/app_flowy/lib/plugins/doc/application/share_service.dart b/frontend/app_flowy/lib/plugins/doc/application/share_service.dart deleted file mode 100644 index 75d045199b..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/application/share_service.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'dart:async'; -import 'package:dartz/dartz.dart'; -import 'package:flowy_sdk/dispatch/dispatch.dart'; -import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart'; - -class ShareService { - Future> export( - String docId, ExportType type) { - final request = ExportPayloadPB.create() - ..viewId = docId - ..exportType = type; - - return DocumentEventExportDocument(request).send(); - } - - Future> exportText(String docId) { - return export(docId, ExportType.Text); - } - - Future> exportMarkdown(String docId) { - return export(docId, ExportType.Markdown); - } - - Future> exportURL(String docId) { - return export(docId, ExportType.Link); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart b/frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart deleted file mode 100644 index 8bbd5bc0d0..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/style_widgets.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; - -class StyleWidgetBuilder { - static QuillCheckboxBuilder checkbox(AppTheme theme) { - return EditorCheckboxBuilder(theme); - } -} - -class EditorCheckboxBuilder extends QuillCheckboxBuilder { - final AppTheme theme; - - EditorCheckboxBuilder(this.theme); - - @override - Widget build( - {required BuildContext context, - required bool isChecked, - required ValueChanged onChanged}) { - return FlowyEditorCheckbox( - theme: theme, - isChecked: isChecked, - onChanged: onChanged, - ); - } -} - -class FlowyEditorCheckbox extends StatefulWidget { - final bool isChecked; - final ValueChanged onChanged; - final AppTheme theme; - const FlowyEditorCheckbox({ - required this.theme, - required this.isChecked, - required this.onChanged, - Key? key, - }) : super(key: key); - - @override - FlowyEditorCheckboxState createState() => FlowyEditorCheckboxState(); -} - -class FlowyEditorCheckboxState extends State { - late bool isChecked; - - @override - void initState() { - isChecked = widget.isChecked; - super.initState(); - } - - @override - Widget build(BuildContext context) { - final icon = isChecked - ? svgWidget('editor/editor_check') - : svgWidget('editor/editor_uncheck'); - return Align( - alignment: Alignment.centerLeft, - child: FlowyIconButton( - onPressed: () { - isChecked = !isChecked; - widget.onChanged(isChecked); - setState(() {}); - }, - iconPadding: EdgeInsets.zero, - icon: icon, - width: 23, - ), - ); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart deleted file mode 100644 index 280209c64a..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/check_button.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/models/documents/style.dart'; -import 'package:flutter/material.dart'; - -import 'toolbar_icon_button.dart'; - -class FlowyCheckListButton extends StatefulWidget { - const FlowyCheckListButton({ - required this.controller, - required this.attribute, - required this.tooltipText, - this.iconSize = defaultIconSize, - this.fillColor, - this.childBuilder = defaultToggleStyleButtonBuilder, - Key? key, - }) : super(key: key); - - final double iconSize; - - final Color? fillColor; - - final QuillController controller; - - final ToggleStyleButtonBuilder childBuilder; - - final Attribute attribute; - - final String tooltipText; - - @override - FlowyCheckListButtonState createState() => FlowyCheckListButtonState(); -} - -class FlowyCheckListButtonState extends State { - bool? _isToggled; - - Style get _selectionStyle => widget.controller.getSelectionStyle(); - - void _didChangeEditingValue() { - setState(() { - _isToggled = - _getIsToggled(widget.controller.getSelectionStyle().attributes); - }); - } - - @override - void initState() { - super.initState(); - _isToggled = _getIsToggled(_selectionStyle.attributes); - widget.controller.addListener(_didChangeEditingValue); - } - - bool _getIsToggled(Map attrs) { - if (widget.attribute.key == Attribute.list.key) { - final attribute = attrs[widget.attribute.key]; - if (attribute == null) { - return false; - } - return attribute.value == widget.attribute.value || - attribute.value == Attribute.checked.value; - } - return attrs.containsKey(widget.attribute.key); - } - - @override - void didUpdateWidget(covariant FlowyCheckListButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeEditingValue); - widget.controller.addListener(_didChangeEditingValue); - _isToggled = _getIsToggled(_selectionStyle.attributes); - } - } - - @override - void dispose() { - widget.controller.removeListener(_didChangeEditingValue); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ToolbarIconButton( - onPressed: _toggleAttribute, - width: widget.iconSize * kIconButtonFactor, - iconName: 'editor/checkbox', - isToggled: _isToggled ?? false, - tooltipText: widget.tooltipText, - ); - } - - void _toggleAttribute() { - widget.controller.formatSelection(_isToggled! - ? Attribute.clone(Attribute.unchecked, null) - : Attribute.unchecked); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart deleted file mode 100644 index 1f262483a8..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/color_picker.dart +++ /dev/null @@ -1,280 +0,0 @@ -import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/models/documents/style.dart'; -import 'package:flutter_quill/utils/color.dart'; -import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'toolbar_icon_button.dart'; - -class FlowyColorButton extends StatefulWidget { - const FlowyColorButton({ - required this.icon, - required this.controller, - required this.background, - this.iconSize = defaultIconSize, - this.iconTheme, - Key? key, - }) : super(key: key); - - final IconData icon; - final double iconSize; - final bool background; - final QuillController controller; - final QuillIconTheme? iconTheme; - - @override - FlowyColorButtonState createState() => FlowyColorButtonState(); -} - -class FlowyColorButtonState extends State { - late bool _isToggledColor; - late bool _isToggledBackground; - late bool _isWhite; - late bool _isWhitebackground; - - Style get _selectionStyle => widget.controller.getSelectionStyle(); - - void _didChangeEditingValue() { - setState(() { - _isToggledColor = - _getIsToggledColor(widget.controller.getSelectionStyle().attributes); - _isToggledBackground = _getIsToggledBackground( - widget.controller.getSelectionStyle().attributes); - _isWhite = _isToggledColor && - _selectionStyle.attributes['color']!.value == '#ffffff'; - _isWhitebackground = _isToggledBackground && - _selectionStyle.attributes['background']!.value == '#ffffff'; - }); - } - - @override - void initState() { - super.initState(); - _isToggledColor = _getIsToggledColor(_selectionStyle.attributes); - _isToggledBackground = _getIsToggledBackground(_selectionStyle.attributes); - _isWhite = _isToggledColor && - _selectionStyle.attributes['color']!.value == '#ffffff'; - _isWhitebackground = _isToggledBackground && - _selectionStyle.attributes['background']!.value == '#ffffff'; - widget.controller.addListener(_didChangeEditingValue); - } - - bool _getIsToggledColor(Map attrs) { - return attrs.containsKey(Attribute.color.key); - } - - bool _getIsToggledBackground(Map attrs) { - return attrs.containsKey(Attribute.background.key); - } - - @override - void didUpdateWidget(covariant FlowyColorButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeEditingValue); - widget.controller.addListener(_didChangeEditingValue); - _isToggledColor = _getIsToggledColor(_selectionStyle.attributes); - _isToggledBackground = - _getIsToggledBackground(_selectionStyle.attributes); - _isWhite = _isToggledColor && - _selectionStyle.attributes['color']!.value == '#ffffff'; - _isWhitebackground = _isToggledBackground && - _selectionStyle.attributes['background']!.value == '#ffffff'; - } - } - - @override - void dispose() { - widget.controller.removeListener(_didChangeEditingValue); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - final fillColor = _isToggledColor && !widget.background && _isWhite - ? stringToColor('#ffffff') - : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor); - final fillColorBackground = - _isToggledBackground && widget.background && _isWhitebackground - ? stringToColor('#ffffff') - : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor); - - return Tooltip( - message: LocaleKeys.toolbar_highlight.tr(), - showDuration: Duration.zero, - child: QuillIconButton( - highlightElevation: 0, - hoverElevation: 0, - size: widget.iconSize * kIconButtonFactor, - icon: Icon(widget.icon, - size: widget.iconSize, color: theme.iconTheme.color), - fillColor: widget.background ? fillColorBackground : fillColor, - onPressed: _showColorPicker, - ), - ); - } - - void _changeColor(BuildContext context, Color color) { - var hex = color.value.toRadixString(16); - if (hex.startsWith('ff')) { - hex = hex.substring(2); - } - hex = '#$hex'; - widget.controller.formatSelection( - widget.background ? BackgroundAttribute(hex) : ColorAttribute(hex)); - Navigator.of(context).pop(); - } - - void _showColorPicker() { - final style = widget.controller.getSelectionStyle(); - final values = style.values - .where((v) => v.key == Attribute.background.key) - .map((v) => v.value); - int initialColor = 0; - if (values.isNotEmpty) { - assert(values.length == 1); - initialColor = stringToHex(values.first); - } - - StyledDialog( - child: SingleChildScrollView( - child: FlowyColorPicker( - onColorChanged: (color) { - if (color == null) { - widget.controller.formatSelection(BackgroundAttribute(null)); - Navigator.of(context).pop(); - } else { - _changeColor(context, color); - } - }, - initialColor: initialColor, - ), - ), - ).show(context); - } -} - -int stringToHex(String code) { - return int.parse(code.substring(1, 7), radix: 16) + 0xFF000000; -} - -class FlowyColorPicker extends StatefulWidget { - final List colors = [ - 0xffe8e0ff, - 0xffffe7fd, - 0xffffe7ee, - 0xffffefe3, - 0xfffff2cd, - 0xfff5ffdc, - 0xffddffd6, - 0xffdefff1, - ]; - final Function(Color?) onColorChanged; - final int initialColor; - FlowyColorPicker( - {Key? key, required this.onColorChanged, this.initialColor = 0}) - : super(key: key); - - @override - State createState() => _FlowyColorPickerState(); -} - -// if (shrinkWrap) { -// innerContent = IntrinsicWidth(child: IntrinsicHeight(child: innerContent)); -// } -class _FlowyColorPickerState extends State { - @override - Widget build(BuildContext context) { - const double width = 480; - const int crossAxisCount = 6; - const double mainAxisSpacing = 10; - const double crossAxisSpacing = 10; - final numberOfRows = (widget.colors.length / crossAxisCount).ceil(); - - const perRowHeight = - ((width - ((crossAxisCount - 1) * mainAxisSpacing)) / crossAxisCount); - final totalHeight = - numberOfRows * perRowHeight + numberOfRows * crossAxisSpacing; - - return Container( - constraints: BoxConstraints.tightFor(width: width, height: totalHeight), - child: CustomScrollView( - scrollDirection: Axis.vertical, - controller: ScrollController(), - physics: const ClampingScrollPhysics(), - slivers: [ - SliverGrid( - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: crossAxisCount, - mainAxisSpacing: mainAxisSpacing, - crossAxisSpacing: crossAxisSpacing, - childAspectRatio: 1.0, - ), - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - if (widget.colors.length > index) { - final isSelected = - widget.colors[index] == widget.initialColor; - return ColorItem( - color: Color(widget.colors[index]), - onPressed: widget.onColorChanged, - isSelected: isSelected, - ); - } else { - return null; - } - }, - childCount: widget.colors.length, - ), - ), - ], - ), - ); - } -} - -class ColorItem extends StatelessWidget { - final Function(Color?) onPressed; - final bool isSelected; - final Color color; - const ColorItem({ - Key? key, - required this.color, - required this.onPressed, - this.isSelected = false, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - if (!isSelected) { - return RawMaterialButton( - onPressed: () { - onPressed(color); - }, - elevation: 0, - hoverElevation: 0.6, - fillColor: color, - shape: const CircleBorder(), - ); - } else { - return RawMaterialButton( - shape: const CircleBorder( - side: BorderSide(color: Colors.white, width: 8)) + - CircleBorder(side: BorderSide(color: color, width: 4)), - onPressed: () { - if (isSelected) { - onPressed(null); - } else { - onPressed(color); - } - }, - elevation: 1.0, - hoverElevation: 0.6, - fillColor: color, - ); - } - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart deleted file mode 100644 index 2db98b5af0..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/header_button.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/models/documents/style.dart'; -import 'package:flutter/material.dart'; -import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'toolbar_icon_button.dart'; - -class FlowyHeaderStyleButton extends StatefulWidget { - const FlowyHeaderStyleButton({ - required this.controller, - this.iconSize = defaultIconSize, - Key? key, - }) : super(key: key); - - final QuillController controller; - final double iconSize; - - @override - FlowyHeaderStyleButtonState createState() => FlowyHeaderStyleButtonState(); -} - -class FlowyHeaderStyleButtonState extends State { - Attribute? _value; - - Style get _selectionStyle => widget.controller.getSelectionStyle(); - - @override - void initState() { - super.initState(); - setState(() { - _value = - _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header; - }); - widget.controller.addListener(_didChangeEditingValue); - } - - @override - Widget build(BuildContext context) { - final valueToText = { - Attribute.h1: 'H1', - Attribute.h2: 'H2', - Attribute.h3: 'H3', - }; - - final valueAttribute = [ - Attribute.h1, - Attribute.h2, - Attribute.h3 - ]; - final valueString = ['H1', 'H2', 'H3']; - final attributeImageName = ['editor/H1', 'editor/H2', 'editor/H3']; - - return Row( - mainAxisSize: MainAxisSize.min, - children: List.generate(3, (index) { - // final child = - // _valueToText[_value] == _valueString[index] ? svg('editor/H1', color: Colors.white) : svg('editor/H1'); - - final headerTitle = "${LocaleKeys.toolbar_header.tr()} ${index + 1}"; - final isToggled = valueToText[_value] == valueString[index]; - return ToolbarIconButton( - onPressed: () { - if (isToggled) { - widget.controller.formatSelection(Attribute.header); - } else { - widget.controller.formatSelection(valueAttribute[index]); - } - }, - width: widget.iconSize * kIconButtonFactor, - iconName: attributeImageName[index], - isToggled: isToggled, - tooltipText: headerTitle, - ); - }), - ); - } - - void _didChangeEditingValue() { - setState(() { - _value = - _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header; - }); - } - - @override - void didUpdateWidget(covariant FlowyHeaderStyleButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeEditingValue); - widget.controller.addListener(_didChangeEditingValue); - _value = - _selectionStyle.attributes[Attribute.header.key] ?? Attribute.header; - } - } - - @override - void dispose() { - widget.controller.removeListener(_didChangeEditingValue); - super.dispose(); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart deleted file mode 100644 index fbe85f40dd..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/history_button.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; - -class FlowyHistoryButton extends StatelessWidget { - final IconData icon; - final double iconSize; - final bool undo; - final QuillController controller; - final String tooltipText; - - const FlowyHistoryButton({ - required this.icon, - required this.controller, - required this.undo, - required this.tooltipText, - required this.iconSize, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Tooltip( - message: tooltipText, - showDuration: Duration.zero, - child: HistoryButton( - icon: icon, - iconSize: iconSize, - controller: controller, - undo: undo, - ), - ); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart deleted file mode 100644 index 1a18ef10f1..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/image_button.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter/material.dart'; -import 'toolbar_icon_button.dart'; - -class FlowyImageButton extends StatelessWidget { - const FlowyImageButton({ - required this.controller, - required this.tooltipText, - this.iconSize = defaultIconSize, - this.onImagePickCallback, - this.fillColor, - this.filePickImpl, - this.webImagePickImpl, - this.mediaPickSettingSelector, - Key? key, - }) : super(key: key); - - final double iconSize; - - final Color? fillColor; - - final QuillController controller; - - final OnImagePickCallback? onImagePickCallback; - - final WebImagePickImpl? webImagePickImpl; - - final FilePickImpl? filePickImpl; - - final MediaPickSettingSelector? mediaPickSettingSelector; - - final String tooltipText; - - @override - Widget build(BuildContext context) { - return ToolbarIconButton( - iconName: 'editor/image', - width: iconSize * 1.77, - onPressed: () => _onPressedHandler(context), - isToggled: false, - tooltipText: tooltipText, - ); - } - - Future _onPressedHandler(BuildContext context) async { - // if (onImagePickCallback != null) { - // final selector = mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting; - // final source = await selector(context); - // if (source != null) { - // if (source == MediaPickSetting.Gallery) { - // _pickImage(context); - // } else { - // _typeLink(context); - // } - // } - // } else { - // _typeLink(context); - // } - } - - // void _pickImage(BuildContext context) => ImageVideoUtils.handleImageButtonTap( - // context, - // controller, - // ImageSource.gallery, - // onImagePickCallback!, - // filePickImpl: filePickImpl, - // webImagePickImpl: webImagePickImpl, - // ); - - // void _typeLink(BuildContext context) { - // TextFieldDialog( - // title: 'URL', - // value: "", - // confirm: (newValue) { - // if (newValue.isEmpty) { - // return; - // } - // final index = controller.selection.baseOffset; - // final length = controller.selection.extentOffset - index; - - // controller.replaceText(index, length, BlockEmbed.image(newValue), null); - // }, - // ).show(context); - // } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart deleted file mode 100644 index 22c9f108ec..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/link_button.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -import 'toolbar_icon_button.dart'; - -class FlowyLinkStyleButton extends StatefulWidget { - const FlowyLinkStyleButton({ - required this.controller, - this.iconSize = defaultIconSize, - Key? key, - }) : super(key: key); - - final QuillController controller; - final double iconSize; - - @override - FlowyLinkStyleButtonState createState() => FlowyLinkStyleButtonState(); -} - -class FlowyLinkStyleButtonState extends State { - void _didChangeSelection() { - setState(() {}); - } - - @override - void initState() { - super.initState(); - widget.controller.addListener(_didChangeSelection); - } - - @override - void didUpdateWidget(covariant FlowyLinkStyleButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeSelection); - widget.controller.addListener(_didChangeSelection); - } - } - - @override - void dispose() { - super.dispose(); - widget.controller.removeListener(_didChangeSelection); - } - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - final isEnabled = !widget.controller.selection.isCollapsed; - final pressedHandler = isEnabled ? () => _openLinkDialog(context) : null; - final icon = isEnabled - ? svgWidget( - 'editor/share', - color: theme.iconColor, - ) - : svgWidget( - 'editor/share', - color: theme.disableIconColor, - ); - - return FlowyIconButton( - onPressed: pressedHandler, - iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), - icon: icon, - fillColor: theme.shader6, - hoverColor: theme.shader5, - width: widget.iconSize * kIconButtonFactor, - ); - } - - void _openLinkDialog(BuildContext context) { - final style = widget.controller.getSelectionStyle(); - final values = style.values - .where((v) => v.key == Attribute.link.key) - .map((v) => v.value); - String value = ""; - if (values.isNotEmpty) { - assert(values.length == 1); - value = values.first; - } - - NavigatorTextFieldDialog( - title: 'URL', - value: value, - confirm: (newValue) { - if (newValue.isEmpty) { - return; - } - widget.controller.formatSelection(LinkAttribute(newValue)); - }, - ).show(context); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart deleted file mode 100644 index 2ecb98c3ea..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toggle_button.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_quill/models/documents/style.dart'; -import 'package:flutter/material.dart'; - -import 'toolbar_icon_button.dart'; - -class FlowyToggleStyleButton extends StatefulWidget { - final Attribute attribute; - final String normalIcon; - final double iconSize; - final QuillController controller; - final String tooltipText; - - const FlowyToggleStyleButton({ - required this.attribute, - required this.normalIcon, - required this.controller, - required this.tooltipText, - this.iconSize = defaultIconSize, - Key? key, - }) : super(key: key); - - @override - ToggleStyleButtonState createState() => ToggleStyleButtonState(); -} - -class ToggleStyleButtonState extends State { - bool? _isToggled; - Style get _selectionStyle => widget.controller.getSelectionStyle(); - @override - void initState() { - super.initState(); - _isToggled = _getIsToggled(_selectionStyle.attributes); - widget.controller.addListener(_didChangeEditingValue); - } - - @override - Widget build(BuildContext context) { - return ToolbarIconButton( - onPressed: _toggleAttribute, - width: widget.iconSize * kIconButtonFactor, - isToggled: _isToggled ?? false, - iconName: widget.normalIcon, - tooltipText: widget.tooltipText, - ); - } - - @override - void didUpdateWidget(covariant FlowyToggleStyleButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeEditingValue); - widget.controller.addListener(_didChangeEditingValue); - _isToggled = _getIsToggled(_selectionStyle.attributes); - } - } - - @override - void dispose() { - widget.controller.removeListener(_didChangeEditingValue); - super.dispose(); - } - - void _didChangeEditingValue() { - setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes)); - } - - bool _getIsToggled(Map attrs) { - if (widget.attribute.key == Attribute.list.key) { - final attribute = attrs[widget.attribute.key]; - if (attribute == null) { - return false; - } - return attribute.value == widget.attribute.value; - } - return attrs.containsKey(widget.attribute.key); - } - - void _toggleAttribute() { - widget.controller.formatSelection(_isToggled! - ? Attribute.clone(widget.attribute, null) - : widget.attribute); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart deleted file mode 100644 index d649340066..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/tool_bar.dart +++ /dev/null @@ -1,308 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter/material.dart'; -import 'package:styled_widget/styled_widget.dart'; -import 'check_button.dart'; -import 'color_picker.dart'; -import 'header_button.dart'; -import 'history_button.dart'; -import 'link_button.dart'; -import 'toggle_button.dart'; -import 'toolbar_icon_button.dart'; -import 'package:app_flowy/generated/locale_keys.g.dart'; - -class EditorToolbar extends StatelessWidget implements PreferredSizeWidget { - final List children; - final double toolBarHeight; - final Color? color; - - const EditorToolbar({ - required this.children, - this.toolBarHeight = 46, - this.color, - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - color: Theme.of(context).canvasColor, - constraints: BoxConstraints.tightFor(height: preferredSize.height), - child: ToolbarButtonList(buttons: children) - .padding(horizontal: 4, vertical: 4), - ); - } - - @override - Size get preferredSize => Size.fromHeight(toolBarHeight); - - factory EditorToolbar.basic({ - required QuillController controller, - double toolbarIconSize = defaultIconSize, - OnImagePickCallback? onImagePickCallback, - OnVideoPickCallback? onVideoPickCallback, - MediaPickSettingSelector? mediaPickSettingSelector, - FilePickImpl? filePickImpl, - WebImagePickImpl? webImagePickImpl, - WebVideoPickImpl? webVideoPickImpl, - Key? key, - }) { - return EditorToolbar( - key: key, - toolBarHeight: toolbarIconSize * 2, - children: [ - FlowyHistoryButton( - icon: Icons.undo_outlined, - iconSize: toolbarIconSize, - controller: controller, - undo: true, - tooltipText: LocaleKeys.toolbar_undo.tr(), - ), - FlowyHistoryButton( - icon: Icons.redo_outlined, - iconSize: toolbarIconSize, - controller: controller, - undo: false, - tooltipText: LocaleKeys.toolbar_redo.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.bold, - normalIcon: 'editor/bold', - iconSize: toolbarIconSize, - controller: controller, - tooltipText: LocaleKeys.toolbar_bold.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.italic, - normalIcon: 'editor/italic', - iconSize: toolbarIconSize, - controller: controller, - tooltipText: LocaleKeys.toolbar_italic.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.underline, - normalIcon: 'editor/underline', - iconSize: toolbarIconSize, - controller: controller, - tooltipText: LocaleKeys.toolbar_underline.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.strikeThrough, - normalIcon: 'editor/strikethrough', - iconSize: toolbarIconSize, - controller: controller, - tooltipText: LocaleKeys.toolbar_strike.tr(), - ), - FlowyColorButton( - icon: Icons.format_color_fill, - iconSize: toolbarIconSize, - controller: controller, - background: true, - ), - // FlowyImageButton( - // iconSize: toolbarIconSize, - // controller: controller, - // onImagePickCallback: onImagePickCallback, - // filePickImpl: filePickImpl, - // webImagePickImpl: webImagePickImpl, - // mediaPickSettingSelector: mediaPickSettingSelector, - // ), - FlowyHeaderStyleButton( - controller: controller, - iconSize: toolbarIconSize, - ), - FlowyToggleStyleButton( - attribute: Attribute.ol, - controller: controller, - normalIcon: 'editor/numbers', - iconSize: toolbarIconSize, - tooltipText: LocaleKeys.toolbar_numList.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.ul, - controller: controller, - normalIcon: 'editor/bullet_list', - iconSize: toolbarIconSize, - tooltipText: LocaleKeys.toolbar_bulletList.tr(), - ), - FlowyCheckListButton( - attribute: Attribute.unchecked, - controller: controller, - iconSize: toolbarIconSize, - tooltipText: LocaleKeys.toolbar_checkList.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.inlineCode, - controller: controller, - normalIcon: 'editor/inline_block', - iconSize: toolbarIconSize, - tooltipText: LocaleKeys.toolbar_inlineCode.tr(), - ), - FlowyToggleStyleButton( - attribute: Attribute.blockQuote, - controller: controller, - normalIcon: 'editor/quote', - iconSize: toolbarIconSize, - tooltipText: LocaleKeys.toolbar_quote.tr(), - ), - FlowyLinkStyleButton( - controller: controller, - iconSize: toolbarIconSize, - ), - FlowyEmojiStyleButton( - normalIcon: 'editor/insert_emoticon', - controller: controller, - tooltipText: "Emoji Picker", - ), - ], - ); - } -} - -class ToolbarButtonList extends StatefulWidget { - const ToolbarButtonList({required this.buttons, Key? key}) : super(key: key); - - final List buttons; - - @override - ToolbarButtonListState createState() => ToolbarButtonListState(); -} - -class ToolbarButtonListState extends State - with WidgetsBindingObserver { - final ScrollController _controller = ScrollController(); - bool _showLeftArrow = false; - bool _showRightArrow = false; - - @override - void initState() { - super.initState(); - _controller.addListener(_handleScroll); - - // Listening to the WidgetsBinding instance is necessary so that we can - // hide the arrows when the window gets a new size and thus the toolbar - // becomes scrollable/unscrollable. - WidgetsBinding.instance.addObserver(this); - - // Workaround to allow the scroll controller attach to our ListView so that - // we can detect if overflow arrows need to be shown on init. - Timer.run(_handleScroll); - } - - @override - Widget build(BuildContext context) { - return LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - List children = []; - double width = - (widget.buttons.length + 2) * defaultIconSize * kIconButtonFactor; - final isFit = constraints.maxWidth > width; - if (!isFit) { - children.add(_buildLeftArrow()); - width = width + 18; - } - - children.add(_buildScrollableList(constraints, isFit)); - - if (!isFit) { - children.add(_buildRightArrow()); - width = width + 18; - } - - return SizedBox( - width: min(constraints.maxWidth, width), - child: Row( - children: children, - ), - ); - }, - ); - } - - @override - void didChangeMetrics() => _handleScroll(); - - @override - void dispose() { - _controller.dispose(); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - void _handleScroll() { - if (!mounted) return; - setState(() { - _showLeftArrow = - _controller.position.minScrollExtent != _controller.position.pixels; - _showRightArrow = - _controller.position.maxScrollExtent != _controller.position.pixels; - }); - } - - Widget _buildLeftArrow() { - return SizedBox( - width: 8, - child: Transform.translate( - // Move the icon a few pixels to center it - offset: const Offset(-5, 0), - child: _showLeftArrow ? const Icon(Icons.arrow_left, size: 18) : null, - ), - ); - } - - // [[sliver: https://medium.com/flutter/slivers-demystified-6ff68ab0296f]] - Widget _buildScrollableList(BoxConstraints constraints, bool isFit) { - Widget child = Expanded( - child: CustomScrollView( - scrollDirection: Axis.horizontal, - controller: _controller, - physics: const ClampingScrollPhysics(), - slivers: [ - SliverList( - delegate: SliverChildBuilderDelegate( - (BuildContext context, int index) { - return widget.buttons[index]; - }, - childCount: widget.buttons.length, - addAutomaticKeepAlives: false, - ), - ) - ], - ), - ); - - if (!isFit) { - child = ScrollConfiguration( - // Remove the glowing effect, as we already have the arrow indicators - behavior: _NoGlowBehavior(), - // The CustomScrollView is necessary so that the children are not - // stretched to the height of the toolbar, https://bit.ly/3uC3bjI - child: child, - ); - } - - return child; - } - - Widget _buildRightArrow() { - return SizedBox( - width: 8, - child: Transform.translate( - // Move the icon a few pixels to center it - offset: const Offset(-5, 0), - child: _showRightArrow ? const Icon(Icons.arrow_right, size: 18) : null, - ), - ); - } -} - -class _NoGlowBehavior extends ScrollBehavior { - @override - Widget buildViewportChrome(BuildContext _, Widget child, AxisDirection __) { - return child; - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart b/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart deleted file mode 100644 index aac5b5a4b1..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/presentation/toolbar/toolbar_icon_button.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flutter/material.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:provider/provider.dart'; - -const double defaultIconSize = 18; - -class ToolbarIconButton extends StatelessWidget { - final double width; - final VoidCallback? onPressed; - final bool isToggled; - final String iconName; - final String tooltipText; - - const ToolbarIconButton({ - Key? key, - required this.onPressed, - required this.isToggled, - required this.width, - required this.iconName, - required this.tooltipText, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final theme = context.watch(); - return FlowyIconButton( - iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4), - onPressed: onPressed, - width: width, - icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor), - fillColor: isToggled == true ? theme.main1 : theme.shader6, - hoverColor: isToggled == true ? theme.main1 : theme.hover, - tooltipText: tooltipText, - ); - } -} diff --git a/frontend/app_flowy/lib/plugins/doc/styles.dart b/frontend/app_flowy/lib/plugins/doc/styles.dart deleted file mode 100644 index d3dee3f97a..0000000000 --- a/frontend/app_flowy/lib/plugins/doc/styles.dart +++ /dev/null @@ -1,133 +0,0 @@ -import 'package:app_flowy/plugins/doc/presentation/style_widgets.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; -import 'package:provider/provider.dart'; -import 'package:tuple/tuple.dart'; -import 'package:flowy_infra/theme.dart'; - -DefaultStyles customStyles(BuildContext context) { - const baseSpacing = Tuple2(6, 0); - - final theme = context.watch(); - final themeData = theme.themeData; - final fontFamily = makeFontFamily(themeData); - - final defaultTextStyle = DefaultTextStyle.of(context); - final baseStyle = defaultTextStyle.style.copyWith( - fontSize: 18, - height: 1.3, - fontWeight: FontWeight.w300, - letterSpacing: 0.6, - fontFamily: fontFamily, - ); - - return DefaultStyles( - h1: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( - fontSize: 34, - color: defaultTextStyle.style.color!.withOpacity(0.70), - height: 1.15, - fontWeight: FontWeight.w300, - ), - const Tuple2(16, 0), - const Tuple2(0, 0), - null), - h2: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( - fontSize: 24, - color: defaultTextStyle.style.color!.withOpacity(0.70), - height: 1.15, - fontWeight: FontWeight.normal, - ), - const Tuple2(8, 0), - const Tuple2(0, 0), - null), - h3: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( - fontSize: 20, - color: defaultTextStyle.style.color!.withOpacity(0.70), - height: 1.25, - fontWeight: FontWeight.w500, - ), - const Tuple2(8, 0), - const Tuple2(0, 0), - null), - paragraph: DefaultTextBlockStyle( - baseStyle, const Tuple2(10, 0), const Tuple2(0, 0), null), - bold: const TextStyle(fontWeight: FontWeight.bold), - italic: const TextStyle(fontStyle: FontStyle.italic), - small: const TextStyle(fontSize: 12, color: Colors.black45), - underline: const TextStyle(decoration: TextDecoration.underline), - strikeThrough: const TextStyle(decoration: TextDecoration.lineThrough), - inlineCode: TextStyle( - color: Colors.blue.shade900.withOpacity(0.9), - fontFamily: fontFamily, - fontSize: 13, - ), - link: TextStyle( - color: themeData.colorScheme.secondary, - decoration: TextDecoration.underline, - ), - color: theme.textColor, - placeHolder: DefaultTextBlockStyle( - defaultTextStyle.style.copyWith( - fontSize: 20, - height: 1.5, - color: Colors.grey.withOpacity(0.6), - ), - const Tuple2(0, 0), - const Tuple2(0, 0), - null), - lists: DefaultListBlockStyle(baseStyle, baseSpacing, const Tuple2(0, 6), - null, StyleWidgetBuilder.checkbox(theme)), - quote: DefaultTextBlockStyle( - TextStyle(color: baseStyle.color!.withOpacity(0.6)), - baseSpacing, - const Tuple2(6, 2), - BoxDecoration( - border: Border( - left: BorderSide(width: 4, color: theme.shader5), - ), - )), - code: DefaultTextBlockStyle( - TextStyle( - color: Colors.blue.shade900.withOpacity(0.9), - fontFamily: fontFamily, - fontSize: 13, - height: 1.15, - ), - baseSpacing, - const Tuple2(0, 0), - BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(2), - )), - indent: DefaultTextBlockStyle( - baseStyle, baseSpacing, const Tuple2(0, 6), null), - align: DefaultTextBlockStyle( - baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null), - leading: DefaultTextBlockStyle( - baseStyle, const Tuple2(0, 0), const Tuple2(0, 0), null), - sizeSmall: const TextStyle(fontSize: 10), - sizeLarge: const TextStyle(fontSize: 18), - sizeHuge: const TextStyle(fontSize: 22)); -} - -String makeFontFamily(ThemeData themeData) { - String fontFamily; - switch (themeData.platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - fontFamily = 'Mulish'; - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.windows: - case TargetPlatform.linux: - fontFamily = 'Roboto Mono'; - break; - default: - throw UnimplementedError(); - } - return fontFamily; -} diff --git a/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart b/frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart similarity index 52% rename from frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart rename to frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart index 89c87db455..322f253447 100644 --- a/frontend/app_flowy/lib/plugins/doc/application/doc_bloc.dart +++ b/frontend/app_flowy/lib/plugins/document/application/doc_bloc.dart @@ -1,11 +1,12 @@ import 'dart:convert'; import 'package:app_flowy/plugins/trash/application/trash_service.dart'; import 'package:app_flowy/workspace/application/view/view_listener.dart'; -import 'package:app_flowy/plugins/doc/application/doc_service.dart'; +import 'package:app_flowy/plugins/document/application/doc_service.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + show EditorState, Document, Transaction; import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; -import 'package:flutter_quill/flutter_quill.dart' show Document, Delta; import 'package:flowy_sdk/log.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -14,27 +15,26 @@ import 'dart:async'; part 'doc_bloc.freezed.dart'; -typedef FlutterQuillDocument = Document; - class DocumentBloc extends Bloc { final ViewPB view; - final DocumentService service; + final DocumentService _documentService; - final ViewListener listener; - final TrashService trashService; - late FlutterQuillDocument document; + final ViewListener _listener; + final TrashService _trashService; + late EditorState editorState; StreamSubscription? _subscription; DocumentBloc({ required this.view, - required this.service, - required this.listener, - required this.trashService, - }) : super(DocumentState.initial()) { + }) : _documentService = DocumentService(), + _listener = ViewListener(view: view), + _trashService = TrashService(), + super(DocumentState.initial()) { on((event, emit) async { await event.map( initial: (Initial value) async { await _initial(value, emit); + _listenOnViewChange(); }, deleted: (Deleted value) async { emit(state.copyWith(isDeleted: true)); @@ -43,7 +43,7 @@ class DocumentBloc extends Bloc { emit(state.copyWith(isDeleted: false)); }, deletePermanently: (DeletePermanently value) async { - final result = await trashService + final result = await _trashService .deleteViews([Tuple2(view.id, TrashType.TrashView)]); final newState = result.fold( @@ -51,7 +51,7 @@ class DocumentBloc extends Bloc { emit(newState); }, restorePage: (RestorePage value) async { - final result = await trashService.putback(view.id); + final result = await _trashService.putback(view.id); final newState = result.fold( (l) => state.copyWith(isDeleted: false), (r) => state); emit(newState); @@ -62,18 +62,41 @@ class DocumentBloc extends Bloc { @override Future close() async { - await listener.stop(); + await _listener.stop(); if (_subscription != null) { await _subscription?.cancel(); } - await service.closeDocument(docId: view.id); + await _documentService.closeDocument(docId: view.id); return super.close(); } Future _initial(Initial value, Emitter emit) async { - listener.start( + final result = await _documentService.openDocument(view: view); + result.fold( + (block) { + final document = Document.fromJson(jsonDecode(block.snapshot)); + editorState = EditorState(document: document); + _listenOnDocumentChange(); + emit( + state.copyWith( + loadingState: DocumentLoadingState.finish(left(unit)), + ), + ); + }, + (err) { + emit( + state.copyWith( + loadingState: DocumentLoadingState.finish(right(err)), + ), + ); + }, + ); + } + + void _listenOnViewChange() { + _listener.start( onViewDeleted: (result) { result.fold( (view) => add(const DocumentEvent.deleted()), @@ -87,46 +110,20 @@ class DocumentBloc extends Bloc { ); }, ); - final result = await service.openDocument(docId: view.id); - result.fold( - (block) { - document = _decodeJsonToDocument(block.snapshot); - _subscription = document.changes.listen((event) { - final delta = event.item2; - final documentDelta = document.toDelta(); - _composeDelta(delta, documentDelta); - }); - emit(state.copyWith( - loadingState: DocumentLoadingState.finish(left(unit)))); - }, - (err) { - emit(state.copyWith( - loadingState: DocumentLoadingState.finish(right(err)))); - }, - ); } - // Document _decodeListToDocument(Uint8List data) { - // final json = jsonDecode(utf8.decode(data)); - // final document = Document.fromJson(json); - // return document; - // } - - void _composeDelta(Delta composedDelta, Delta documentDelta) async { - final json = jsonEncode(composedDelta.toJson()); - Log.debug("doc_id: $view.id - Send json: $json"); - final result = await service.applyEdit(docId: view.id, data: json); - - result.fold( - (_) {}, - (r) => Log.error(r), - ); - } - - Document _decodeJsonToDocument(String data) { - final json = jsonDecode(data); - final document = Document.fromJson(json); - return document; + void _listenOnDocumentChange() { + _subscription = editorState.transactionStream.listen((transaction) { + final json = jsonEncode(TransactionAdaptor(transaction).toJson()); + _documentService + .applyEdit(docId: view.id, operations: json) + .then((result) { + result.fold( + (l) => null, + (err) => Log.error(err), + ); + }); + }); } } @@ -160,3 +157,44 @@ class DocumentLoadingState with _$DocumentLoadingState { const factory DocumentLoadingState.finish( Either successOrFail) = _Finish; } + +/// Uses to erase the different between appflowy editor and the backend +class TransactionAdaptor { + final Transaction transaction; + TransactionAdaptor(this.transaction); + + Map toJson() { + final json = {}; + if (transaction.operations.isNotEmpty) { + // The backend uses [0,0] as the beginning path, but the editor uses [0]. + // So it needs to extend the path by inserting `0` at the head for all + // operations before passing to the backend. + json['operations'] = transaction.operations + .map((e) => e.copyWith(path: [0, ...e.path]).toJson()) + .toList(); + } + if (transaction.afterSelection != null) { + final selection = transaction.afterSelection!; + final start = selection.start; + final end = selection.end; + json['after_selection'] = selection + .copyWith( + start: start.copyWith(path: [0, ...start.path]), + end: end.copyWith(path: [0, ...end.path]), + ) + .toJson(); + } + if (transaction.beforeSelection != null) { + final selection = transaction.beforeSelection!; + final start = selection.start; + final end = selection.end; + json['before_selection'] = selection + .copyWith( + start: start.copyWith(path: [0, ...start.path]), + end: end.copyWith(path: [0, ...end.path]), + ) + .toJson(); + } + return json; + } +} diff --git a/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart b/frontend/app_flowy/lib/plugins/document/application/doc_service.dart similarity index 63% rename from frontend/app_flowy/lib/plugins/doc/application/doc_service.dart rename to frontend/app_flowy/lib/plugins/document/application/doc_service.dart index 3a6797837f..c967221c9c 100644 --- a/frontend/app_flowy/lib/plugins/doc/application/doc_service.dart +++ b/frontend/app_flowy/lib/plugins/document/application/doc_service.dart @@ -3,28 +3,35 @@ import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-sync/document.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart'; class DocumentService { Future> openDocument({ - required String docId, + required ViewPB view, }) async { - await FolderEventSetLatestView(ViewIdPB(value: docId)).send(); + await FolderEventSetLatestView(ViewIdPB(value: view.id)).send(); + + final payload = OpenDocumentContextPB() + ..documentId = view.id + ..documentVersion = DocumentVersionPB.V1; + // switch (view.dataFormat) { + // case ViewDataFormatPB.DeltaFormat: + // payload.documentVersion = DocumentVersionPB.V0; + // break; + // default: + // break; + // } - final payload = DocumentIdPB(value: docId); return DocumentEventGetDocument(payload).send(); } Future> applyEdit({ required String docId, - required String data, - String operations = "", + required String operations, }) { final payload = EditPayloadPB.create() ..docId = docId - ..operations = operations - ..operationsStr = data; + ..operations = operations; return DocumentEventApplyEdit(payload).send(); } diff --git a/frontend/app_flowy/lib/plugins/doc/application/prelude.dart b/frontend/app_flowy/lib/plugins/document/application/prelude.dart similarity index 100% rename from frontend/app_flowy/lib/plugins/doc/application/prelude.dart rename to frontend/app_flowy/lib/plugins/document/application/prelude.dart diff --git a/frontend/app_flowy/lib/plugins/doc/application/share_bloc.dart b/frontend/app_flowy/lib/plugins/document/application/share_bloc.dart similarity index 56% rename from frontend/app_flowy/lib/plugins/doc/application/share_bloc.dart rename to frontend/app_flowy/lib/plugins/document/application/share_bloc.dart index fa7dbb761a..c74cdb8471 100644 --- a/frontend/app_flowy/lib/plugins/doc/application/share_bloc.dart +++ b/frontend/app_flowy/lib/plugins/document/application/share_bloc.dart @@ -1,14 +1,14 @@ -import 'dart:async'; +import 'dart:convert'; import 'dart:io'; -import 'package:app_flowy/startup/tasks/rust_sdk.dart'; -import 'package:app_flowy/workspace/application/markdown/delta_markdown.dart'; -import 'package:app_flowy/plugins/doc/application/share_service.dart'; +import 'package:app_flowy/plugins/document/application/share_service.dart'; import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + show Document, documentToMarkdown; part 'share_bloc.freezed.dart'; class DocShareBloc extends Bloc { @@ -18,11 +18,14 @@ class DocShareBloc extends Bloc { : super(const DocShareState.initial()) { on((event, emit) async { await event.map( - shareMarkdown: (ShareMarkdown value) async { - await service.exportMarkdown(view.id).then((result) { + shareMarkdown: (ShareMarkdown shareMarkdown) async { + await service.exportMarkdown(view).then((result) { result.fold( (value) => emit( - DocShareState.finish(left(_convertDeltaToMarkdown(value)))), + DocShareState.finish( + left(_saveMarkdown(value, shareMarkdown.path)), + ), + ), (error) => emit(DocShareState.finish(right(error))), ); }); @@ -35,38 +38,23 @@ class DocShareBloc extends Bloc { }); } - ExportDataPB _convertDeltaToMarkdown(ExportDataPB value) { - final result = deltaToMarkdown(value.data); - value.data = result; - writeFile(result); + ExportDataPB _saveMarkdown(ExportDataPB value, String path) { + final markdown = _convertDocumentToMarkdown(value); + value.data = markdown; + File(path).writeAsStringSync(markdown); return value; } - Future get _exportDir async { - Directory documentsDir = await appFlowyDocumentDirectory(); - - return documentsDir; - } - - Future get _localPath async { - final dir = await _exportDir; - return dir.path; - } - - Future get _localFile async { - final path = await _localPath; - return File('$path/${view.name}.md'); - } - - Future writeFile(String md) async { - final file = await _localFile; - return file.writeAsString(md); + String _convertDocumentToMarkdown(ExportDataPB value) { + final json = jsonDecode(value.data); + final document = Document.fromJson(json); + return documentToMarkdown(document); } } @freezed class DocShareEvent with _$DocShareEvent { - const factory DocShareEvent.shareMarkdown() = ShareMarkdown; + const factory DocShareEvent.shareMarkdown(String path) = ShareMarkdown; const factory DocShareEvent.shareText() = ShareText; const factory DocShareEvent.shareLink() = ShareLink; } diff --git a/frontend/app_flowy/lib/plugins/document/application/share_service.dart b/frontend/app_flowy/lib/plugins/document/application/share_service.dart new file mode 100644 index 0000000000..5bb0ef0d4d --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/application/share_service.dart @@ -0,0 +1,30 @@ +import 'dart:async'; +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; + +class ShareService { + Future> export( + ViewPB view, ExportType type) { + var payload = ExportPayloadPB.create() + ..viewId = view.id + ..exportType = type + ..documentVersion = DocumentVersionPB.V1; + + return DocumentEventExportDocument(payload).send(); + } + + Future> exportText(ViewPB view) { + return export(view, ExportType.Text); + } + + Future> exportMarkdown(ViewPB view) { + return export(view, ExportType.Markdown); + } + + Future> exportURL(ViewPB view) { + return export(view, ExportType.Link); + } +} diff --git a/frontend/app_flowy/lib/plugins/doc/document.dart b/frontend/app_flowy/lib/plugins/document/document.dart similarity index 81% rename from frontend/app_flowy/lib/plugins/doc/document.dart rename to frontend/app_flowy/lib/plugins/document/document.dart index 55296f7831..9ad6107dcf 100644 --- a/frontend/app_flowy/lib/plugins/doc/document.dart +++ b/frontend/app_flowy/lib/plugins/document/document.dart @@ -4,8 +4,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/util.dart'; import 'package:app_flowy/startup/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/appearance.dart'; -import 'package:app_flowy/plugins/doc/application/share_bloc.dart'; +import 'package:app_flowy/plugins/document/application/share_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/toast.dart'; import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart'; @@ -14,8 +13,8 @@ import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:clipboard/clipboard.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:file_picker/file_picker.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; @@ -23,7 +22,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-document/entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; import 'document_page.dart'; @@ -40,11 +38,14 @@ class DocumentPluginBuilder extends PluginBuilder { @override String get menuName => LocaleKeys.document_menuName.tr(); + @override + String get menuIcon => "editor/documents"; + @override PluginType get pluginType => PluginType.editor; @override - ViewDataTypePB get dataType => ViewDataTypePB.Text; + ViewDataFormatPB get dataFormatType => ViewDataFormatPB.TreeFormat; } class DocumentPlugin extends Plugin { @@ -128,21 +129,13 @@ class DocumentShareButton extends StatelessWidget { ); }, child: BlocBuilder( - builder: (context, state) { - return ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: Selector( - selector: (ctx, notifier) => notifier.locale, - builder: (ctx, _, child) => ConstrainedBox( - constraints: const BoxConstraints.expand( - height: 30, - width: 100, - ), - child: const ShareActionList(), - ), - ), - ); - }, + builder: (context, state) => ConstrainedBox( + constraints: const BoxConstraints.expand( + height: 30, + width: 100, + ), + child: ShareActionList(view: view), + ), ), ), ); @@ -165,11 +158,16 @@ class DocumentShareButton extends StatelessWidget { } class ShareActionList extends StatelessWidget { - const ShareActionList({Key? key}) : super(key: key); + const ShareActionList({ + Key? key, + required this.view, + }) : super(key: key); + + final ViewPB view; @override Widget build(BuildContext context) { - final theme = context.watch(); + final docShareBloc = context.read(); return PopoverActionList( direction: PopoverDirection.bottomWithCenterAligned, actions: ShareAction.values @@ -178,20 +176,23 @@ class ShareActionList extends StatelessWidget { buildChild: (controller) { return RoundedTextButton( title: LocaleKeys.shareAction_buttonText.tr(), - fontSize: 12, + fontSize: FontSizes.s12, borderRadius: Corners.s6Border, - color: theme.main1, + color: Theme.of(context).colorScheme.primary, onPressed: () => controller.show(), ); }, - onSelected: (action, controller) { + onSelected: (action, controller) async { switch (action.inner) { case ShareAction.markdown: - context - .read() - .add(const DocShareEvent.shareMarkdown()); - showMessageToast( - 'Exported to: ${LocaleKeys.notifications_export_path.tr()}'); + final exportPath = await FilePicker.platform.saveFile( + dialogTitle: '', + fileName: '${view.name}.md', + ); + if (exportPath != null) { + docShareBloc.add(DocShareEvent.shareMarkdown(exportPath)); + showMessageToast('Exported to: $exportPath'); + } break; case ShareAction.copyLink: NavigatorAlertDialog( diff --git a/frontend/app_flowy/lib/plugins/doc/document_page.dart b/frontend/app_flowy/lib/plugins/document/document_page.dart similarity index 52% rename from frontend/app_flowy/lib/plugins/doc/document_page.dart rename to frontend/app_flowy/lib/plugins/document/document_page.dart index 8593766087..cc4b31f582 100644 --- a/frontend/app_flowy/lib/plugins/doc/document_page.dart +++ b/frontend/app_flowy/lib/plugins/document/document_page.dart @@ -1,17 +1,14 @@ +import 'package:app_flowy/plugins/document/editor_styles.dart'; +import 'package:app_flowy/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart'; import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/appearance.dart'; -import 'package:app_flowy/plugins/doc/presentation/banner.dart'; -import 'package:app_flowy/plugins/doc/presentation/toolbar/tool_bar.dart'; -import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter_quill/flutter_quill.dart' as quill; +import 'package:app_flowy/plugins/document/presentation/banner.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; +import 'package:intl/intl.dart'; import 'application/doc_bloc.dart'; -import 'styles.dart'; class DocumentPage extends StatefulWidget { final VoidCallback onDeleted; @@ -29,11 +26,12 @@ class DocumentPage extends StatefulWidget { class _DocumentPageState extends State { late DocumentBloc documentBloc; - final scrollController = ScrollController(); final FocusNode _focusNode = FocusNode(); @override void initState() { + // The appflowy editor use Intl as localization, set the default language as fallback. + Intl.defaultLocale = 'en_US'; documentBloc = getIt(param1: super.widget.view) ..add(const DocumentEvent.initial()); super.initState(); @@ -48,9 +46,9 @@ class _DocumentPageState extends State { child: BlocBuilder(builder: (context, state) { return state.loadingState.map( - // loading: (_) => const FlowyProgressIndicator(), - loading: (_) => - SizedBox.expand(child: Container(color: Colors.transparent)), + loading: (_) => SizedBox.expand( + child: Container(color: Colors.transparent), + ), finish: (result) => result.successOrFail.fold( (_) { if (state.forceClose) { @@ -75,23 +73,12 @@ class _DocumentPageState extends State { } Widget _renderDocument(BuildContext context, DocumentState state) { - quill.QuillController controller = quill.QuillController( - document: context.read().document, - selection: const TextSelection.collapsed(offset: 0), - ); return Column( children: [ if (state.isDeleted) _renderBanner(context), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _renderEditor(controller), - const VSpace(10), - _renderToolbar(controller), - const VSpace(10), - ], - ), + // AppFlowy Editor + _renderAppFlowyEditor( + context.read().editorState, ), ], ); @@ -107,36 +94,26 @@ class _DocumentPageState extends State { ); } - Widget _renderEditor(quill.QuillController controller) { - final editor = quill.QuillEditor( - controller: controller, - focusNode: _focusNode, - scrollable: true, - paintCursorAboveText: true, - autoFocus: controller.document.isEmpty(), - expands: false, - padding: const EdgeInsets.symmetric(horizontal: 8.0), - readOnly: false, - scrollBottomInset: 0, - scrollController: scrollController, - customStyles: customStyles(context), + Widget _renderAppFlowyEditor(EditorState editorState) { + final theme = Theme.of(context); + final editor = AppFlowyEditor( + editorState: editorState, + autoFocus: editorState.document.isEmpty, + customBuilders: { + 'horizontal_rule': HorizontalRuleWidgetBuilder(), + }, + shortcutEvents: [ + insertHorizontalRule, + ], + themeData: theme.copyWith(extensions: [ + ...theme.extensions.values, + customEditorTheme(context), + ...customPluginTheme(context), + ]), ); - return Expanded( - child: ScrollbarListStack( - axis: Axis.vertical, - controller: scrollController, - barSize: 6.0, - child: SizedBox.expand(child: editor), - ), - ); - } - - Widget _renderToolbar(quill.QuillController controller) { - return ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: EditorToolbar.basic( - controller: controller, + child: SizedBox.expand( + child: editor, ), ); } diff --git a/frontend/app_flowy/lib/plugins/document/editor_styles.dart b/frontend/app_flowy/lib/plugins/document/editor_styles.dart new file mode 100644 index 0000000000..00cbcb473e --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/editor_styles.dart @@ -0,0 +1,65 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +const _baseFontSize = 14.0; + +EditorStyle customEditorTheme(BuildContext context) { + var editorStyle = Theme.of(context).brightness == Brightness.dark + ? EditorStyle.dark + : EditorStyle.light; + editorStyle = editorStyle.copyWith( + textStyle: editorStyle.textStyle?.copyWith( + fontFamily: 'poppins', + fontSize: _baseFontSize, + ), + placeholderTextStyle: editorStyle.placeholderTextStyle?.copyWith( + fontFamily: 'poppins', + fontSize: _baseFontSize, + ), + bold: editorStyle.bold?.copyWith( + fontWeight: FontWeight.w500, + ), + backgroundColor: Theme.of(context).colorScheme.surface, + ); + return editorStyle; +} + +Iterable> customPluginTheme(BuildContext context) { + const basePadding = 12.0; + var headingPluginStyle = Theme.of(context).brightness == Brightness.dark + ? HeadingPluginStyle.dark + : HeadingPluginStyle.light; + headingPluginStyle = headingPluginStyle.copyWith( + textStyle: (EditorState editorState, Node node) { + final headingToFontSize = { + 'h1': _baseFontSize + 12, + 'h2': _baseFontSize + 8, + 'h3': _baseFontSize + 4, + 'h4': _baseFontSize, + 'h5': _baseFontSize, + 'h6': _baseFontSize, + }; + final fontSize = + headingToFontSize[node.attributes.heading] ?? _baseFontSize; + return TextStyle(fontSize: fontSize, fontWeight: FontWeight.w600); + }, + padding: (EditorState editorState, Node node) { + final headingToPadding = { + 'h1': basePadding + 6, + 'h2': basePadding + 4, + 'h3': basePadding + 2, + 'h4': basePadding, + 'h5': basePadding, + 'h6': basePadding, + }; + final padding = headingToPadding[node.attributes.heading] ?? basePadding; + return EdgeInsets.only(bottom: padding); + }, + ); + final pluginTheme = Theme.of(context).brightness == Brightness.dark + ? darkPlguinStyleExtension + : lightPlguinStyleExtension; + return pluginTheme.toList() + ..removeWhere((element) => element is HeadingPluginStyle) + ..add(headingPluginStyle); +} diff --git a/frontend/app_flowy/lib/plugins/doc/presentation/banner.dart b/frontend/app_flowy/lib/plugins/document/presentation/banner.dart similarity index 72% rename from frontend/app_flowy/lib/plugins/doc/presentation/banner.dart rename to frontend/app_flowy/lib/plugins/document/presentation/banner.dart index 66c1f6dfba..bfa5130f21 100644 --- a/frontend/app_flowy/lib/plugins/doc/presentation/banner.dart +++ b/frontend/app_flowy/lib/plugins/document/presentation/banner.dart @@ -1,11 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/buttons/base_styled_button.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; class DocumentBanner extends StatelessWidget { @@ -17,12 +15,11 @@ class DocumentBanner extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return ConstrainedBox( constraints: const BoxConstraints(minHeight: 60), child: Container( width: double.infinity, - color: theme.main1, + color: Theme.of(context).colorScheme.primary, child: FittedBox( alignment: Alignment.center, fit: BoxFit.scaleDown, @@ -36,30 +33,32 @@ class DocumentBanner extends StatelessWidget { minHeight: 40, contentPadding: EdgeInsets.zero, bgColor: Colors.transparent, - hoverColor: theme.main2, - downColor: theme.main1, + hoverColor: Theme.of(context).colorScheme.primary, + downColor: Theme.of(context).colorScheme.primaryContainer, outlineColor: Colors.white, borderRadius: Corners.s8Border, onPressed: onRestore, child: FlowyText.medium( - LocaleKeys.deletePagePrompt_restore.tr(), - color: Colors.white, - fontSize: 14)), + LocaleKeys.deletePagePrompt_restore.tr(), + color: Theme.of(context).colorScheme.onPrimary, + fontSize: 14, + )), const HSpace(20), BaseStyledButton( minWidth: 220, minHeight: 40, contentPadding: EdgeInsets.zero, bgColor: Colors.transparent, - hoverColor: theme.main2, - downColor: theme.main1, + hoverColor: Theme.of(context).colorScheme.primaryContainer, + downColor: Theme.of(context).colorScheme.primary, outlineColor: Colors.white, borderRadius: Corners.s8Border, onPressed: onDelete, child: FlowyText.medium( - LocaleKeys.deletePagePrompt_deletePermanent.tr(), - color: Colors.white, - fontSize: 14)), + LocaleKeys.deletePagePrompt_deletePermanent.tr(), + color: Theme.of(context).colorScheme.onPrimary, + fontSize: 14, + )), ], ), ), diff --git a/frontend/app_flowy/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart b/frontend/app_flowy/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart new file mode 100644 index 0000000000..c3d4cbeb35 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/document/presentation/plugins/horizontal_rule_node_widget.dart @@ -0,0 +1,168 @@ +import 'dart:collection'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +ShortcutEvent insertHorizontalRule = ShortcutEvent( + key: 'Horizontal rule', + command: 'Minus', + handler: _insertHorzaontalRule, +); + +ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { + final selection = editorState.service.selectionService.currentSelection.value; + final textNodes = editorState.service.selectionService.currentSelectedNodes + .whereType(); + if (textNodes.length != 1 || selection == null) { + return KeyEventResult.ignored; + } + final textNode = textNodes.first; + if (textNode.toPlainText() == '--') { + final transaction = editorState.transaction + ..deleteText(textNode, 0, 2) + ..insertNode( + textNode.path, + Node( + type: 'horizontal_rule', + children: LinkedList(), + attributes: {}, + ), + ) + ..afterSelection = + Selection.single(path: textNode.path.next, startOffset: 0); + editorState.apply(transaction); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; +}; + +SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( + name: () => 'Horizontal rule', + icon: (editorState, onSelected) => Icon( + Icons.horizontal_rule, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, + size: 18.0, + ), + keywords: ['horizontal rule'], + handler: (editorState, _, __) { + final selection = + editorState.service.selectionService.currentSelection.value; + final textNodes = editorState.service.selectionService.currentSelectedNodes + .whereType(); + if (selection == null || textNodes.isEmpty) { + return; + } + final textNode = textNodes.first; + if (textNode.toPlainText().isEmpty) { + final transaction = editorState.transaction + ..insertNode( + textNode.path, + Node( + type: 'horizontal_rule', + children: LinkedList(), + attributes: {}, + ), + ) + ..afterSelection = + Selection.single(path: textNode.path.next, startOffset: 0); + editorState.apply(transaction); + } else { + final transaction = editorState.transaction + ..insertNode( + selection.end.path.next, + TextNode( + children: LinkedList(), + attributes: { + 'subtype': 'horizontal_rule', + }, + delta: Delta()..insert('---'), + ), + ) + ..afterSelection = selection; + editorState.apply(transaction); + } + }, +); + +class HorizontalRuleWidgetBuilder extends NodeWidgetBuilder { + @override + Widget build(NodeWidgetContext context) { + return _HorizontalRuleWidget( + key: context.node.key, + node: context.node, + editorState: context.editorState, + ); + } + + @override + NodeValidator get nodeValidator => (node) { + return true; + }; +} + +class _HorizontalRuleWidget extends StatefulWidget { + const _HorizontalRuleWidget({ + Key? key, + required this.node, + required this.editorState, + }) : super(key: key); + + final Node node; + final EditorState editorState; + + @override + State<_HorizontalRuleWidget> createState() => __HorizontalRuleWidgetState(); +} + +class __HorizontalRuleWidgetState extends State<_HorizontalRuleWidget> + with SelectableMixin { + RenderBox get _renderBox => context.findRenderObject() as RenderBox; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Container( + height: 1, + color: Colors.grey, + ), + ); + } + + @override + Position start() => Position(path: widget.node.path, offset: 0); + + @override + Position end() => Position(path: widget.node.path, offset: 1); + + @override + Position getPositionInOffset(Offset start) => end(); + + @override + bool get shouldCursorBlink => false; + + @override + CursorStyle get cursorStyle => CursorStyle.borderLine; + + @override + Rect? getCursorRectInPosition(Position position) { + final size = _renderBox.size; + return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height); + } + + @override + List getRectsInSelection(Selection selection) => + [Offset.zero & _renderBox.size]; + + @override + Selection getSelectionInRange(Offset start, Offset end) => Selection.single( + path: widget.node.path, + startOffset: 0, + endOffset: 1, + ); + + @override + Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset); +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart b/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart index b39226f0be..34bd1ba3dd 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/block/block_cache.dart @@ -13,7 +13,7 @@ class GridBlockCache { late GridRowCache _rowCache; late GridBlockListener _listener; - List get rows => _rowCache.rows; + List get rows => _rowCache.visibleRows; GridRowCache get rowCache => _rowCache; GridBlockCache({ @@ -30,7 +30,7 @@ class GridBlockCache { _listener = GridBlockListener(blockId: block.id); _listener.start((result) { result.fold( - (changesets) => _rowCache.applyChangesets(changesets), + (changeset) => _rowCache.applyChangesets(changeset), (err) => Log.error(err), ); }); diff --git a/frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart index 91f93c61fe..c4770462b3 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/block/block_listener.dart @@ -7,11 +7,12 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart'; -typedef GridBlockUpdateNotifierValue = Either, FlowyError>; +typedef GridBlockUpdateNotifierValue = Either; class GridBlockListener { final String blockId; - PublishNotifier? _rowsUpdateNotifier = PublishNotifier(); + PublishNotifier? _rowsUpdateNotifier = + PublishNotifier(); GridNotificationListener? _listener; GridBlockListener({required this.blockId}); @@ -29,11 +30,12 @@ class GridBlockListener { _rowsUpdateNotifier?.addPublishListener(onBlockChanged); } - void _handler(GridNotification ty, Either result) { + void _handler(GridDartNotification ty, Either result) { switch (ty) { - case GridNotification.DidUpdateGridBlock: + case GridDartNotification.DidUpdateGridBlock: result.fold( - (payload) => _rowsUpdateNotifier?.value = left([GridBlockChangesetPB.fromBuffer(payload)]), + (payload) => _rowsUpdateNotifier?.value = + left(GridBlockChangesetPB.fromBuffer(payload)), (error) => _rowsUpdateNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart index 4805ad8b7a..9d22177933 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_listener.dart @@ -22,9 +22,9 @@ class CellListener { objectId: "$rowId:$fieldId", handler: _handler); } - void _handler(GridNotification ty, Either result) { + void _handler(GridDartNotification ty, Either result) { switch (ty) { - case GridNotification.DidUpdateCell: + case GridDartNotification.DidUpdateCell: result.fold( (payload) => _updateCellNotifier?.value = left(unit), (error) => _updateCellNotifier?.value = right(error), diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart index 7a43378eb8..cdf695dc7f 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_controller.dart @@ -234,7 +234,7 @@ class IGridCellController extends Equatable { return data; } - /// Return the FieldTypeOptionDataPB that can be parsed into corresponding class using the [parser]. + /// Return the TypeOptionPB that can be parsed into corresponding class using the [parser]. /// [PD] is the type that the parser return. Future> getFieldTypeOption(P parser) { @@ -290,20 +290,20 @@ class IGridCellController extends Equatable { }); } - void dispose() { + Future dispose() async { if (_isDispose) { Log.error("$this should only dispose once"); return; } _isDispose = true; - _cellListener?.stop(); + await _cellListener?.stop(); _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); _cellDataNotifier = null; if (_onFieldChangedFn != null) { _fieldNotifier.unregister(_cacheKey, _onFieldChangedFn!); - _fieldNotifier.dispose(); + await _fieldNotifier.dispose(); _onFieldChangedFn = null; } } @@ -329,7 +329,7 @@ class GridCellFieldNotifierImpl extends IGridCellFieldNotifier { @override void onCellFieldChanged(void Function(FieldPB p1) callback) { - _onChangesetFn = (FieldChangesetPB changeset) { + _onChangesetFn = (GridFieldChangesetPB changeset) { for (final updatedField in changeset.updatedFields) { callback(updatedField); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart index a6a1ba43a9..daf404d497 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_loader.dart @@ -25,7 +25,7 @@ class GridCellDataLoader { final fut = service.getCell(cellId: cellId); return fut.then( (result) => result.fold( - (GridCellPB cell) { + (CellPB cell) { try { return parser.parserData(cell.data); } catch (e, s) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart index 71927bae14..253744a28a 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_data_persistence.dart @@ -29,10 +29,12 @@ class CellDataPersistence implements IGridCellDataPersistence { @freezed class CalendarData with _$CalendarData { - const factory CalendarData({required DateTime date, String? time}) = _CalendarData; + const factory CalendarData({required DateTime date, String? time}) = + _CalendarData; } -class DateCellDataPersistence implements IGridCellDataPersistence { +class DateCellDataPersistence + implements IGridCellDataPersistence { final GridCellIdentifier cellId; DateCellDataPersistence({ required this.cellId, @@ -40,10 +42,11 @@ class DateCellDataPersistence implements IGridCellDataPersistence @override Future> save(CalendarData data) { - var payload = DateChangesetPayloadPB.create()..cellIdentifier = _makeCellIdPayload(cellId); + var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId); final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString(); payload.date = date; + payload.isUtc = data.date.isUtc; if (data.time != null) { payload.time = data.time!; @@ -58,8 +61,8 @@ class DateCellDataPersistence implements IGridCellDataPersistence } } -GridCellIdPB _makeCellIdPayload(GridCellIdentifier cellId) { - return GridCellIdPB.create() +CellPathPB _makeCellPath(GridCellIdentifier cellId) { + return CellPathPB.create() ..gridId = cellId.gridId ..fieldId = cellId.fieldId ..rowId = cellId.rowId; diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart index 759b7a1ed7..1e6cbaab11 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/cell_service/cell_service.dart @@ -42,10 +42,10 @@ class CellService { return GridEventUpdateCell(payload).send(); } - Future> getCell({ + Future> getCell({ required GridCellIdentifier cellId, }) { - final payload = GridCellIdPB.create() + final payload = CellPathPB.create() ..gridId = cellId.gridId ..fieldId = cellId.fieldId ..rowId = cellId.rowId; diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart index 5f7aee108e..503dd88b81 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/checkbox_cell_bloc.dart @@ -37,7 +37,7 @@ class CheckboxCellBloc extends Bloc { _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart index 0deee8098c..61de91e21b 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cal_bloc.dart @@ -139,7 +139,7 @@ class DateCalBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart index e44e15a2fa..38f51e710e 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/date_cell_bloc.dart @@ -31,7 +31,7 @@ class DateCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart index 2ca989289f..5a85f007ad 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/number_cell_bloc.dart @@ -46,7 +46,7 @@ class NumberCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart index 8ca25a6050..43cdf613da 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_cell_bloc.dart @@ -36,7 +36,7 @@ class SelectOptionCellBloc cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart index 931e370855..9c3eb9cf23 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_editor_bloc.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; -import 'package:collection/collection.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart'; @@ -46,19 +45,29 @@ class SelectOptionCellEditorBloc )); }, deleteOption: (_DeleteOption value) { - _deleteOption(value.option); + _deleteOption([value.option]); + }, + deleteAllOptions: (_DeleteAllOptions value) { + if (state.allOptions.isNotEmpty) { + _deleteOption(state.allOptions); + } }, updateOption: (_UpdateOption value) { _updateOption(value.option); }, selectOption: (_SelectOption value) { - _onSelectOption(value.optionId); + _selectOptionService.select(optionIds: [value.optionId]); + }, + unSelectOption: (_UnSelectOption value) { + _selectOptionService.unSelect(optionIds: [value.optionId]); }, trySelectOption: (_TrySelectOption value) { _trySelectOption(value.optionName, emit); }, selectMultipleOptions: (_SelectMultipleOptions value) { - _selectMultipleOptions(value.optionNames); + if (value.optionNames.isNotEmpty) { + _selectMultipleOptions(value.optionNames); + } _filterOption(value.remainder, emit); }, filterOption: (_SelectOptionFilter value) { @@ -72,7 +81,7 @@ class SelectOptionCellEditorBloc @override Future close() async { _delayOperation?.cancel(); - cellController.dispose(); + await cellController.dispose(); return super.close(); } @@ -81,11 +90,8 @@ class SelectOptionCellEditorBloc result.fold((l) => {}, (err) => Log.error(err)); } - void _deleteOption(SelectOptionPB option) async { - final result = await _selectOptionService.delete( - option: option, - ); - + void _deleteOption(List options) async { + final result = await _selectOptionService.delete(options: options); result.fold((l) => null, (err) => Log.error(err)); } @@ -97,16 +103,6 @@ class SelectOptionCellEditorBloc result.fold((l) => null, (err) => Log.error(err)); } - void _onSelectOption(String optionId) { - final hasSelected = state.selectedOptions - .firstWhereOrNull((option) => option.id == optionId); - if (hasSelected != null) { - _selectOptionService.unSelect(optionIds: [optionId]); - } else { - _selectOptionService.select(optionIds: [optionId]); - } - } - void _trySelectOption( String optionName, Emitter emit) { SelectOptionPB? matchingOption; @@ -138,9 +134,19 @@ class SelectOptionCellEditorBloc } void _selectMultipleOptions(List optionNames) { - final optionIds = state.options - .where((e) => optionNames.contains(e.name)) - .map((e) => e.id); + // The options are unordered. So in order to keep the inserted [optionNames] + // order, it needs to get the option id in the [optionNames] order. + final lowerCaseNames = optionNames.map((e) => e.toLowerCase()); + final Map optionIdsMap = {}; + for (final option in state.options) { + optionIdsMap[option.name.toLowerCase()] = option.id; + } + + final optionIds = lowerCaseNames + .where((name) => optionIdsMap[name] != null) + .map((name) => optionIdsMap[name]!) + .toList(); + _selectOptionService.select(optionIds: optionIds); } @@ -162,8 +168,10 @@ class SelectOptionCellEditorBloc return; } return result.fold( - (data) => add(SelectOptionEditorEvent.didReceiveOptions( - data.options, data.selectOptions)), + (data) => add( + SelectOptionEditorEvent.didReceiveOptions( + data.options, data.selectOptions), + ), (err) { Log.error(err); return null; @@ -225,10 +233,13 @@ class SelectOptionEditorEvent with _$SelectOptionEditorEvent { _NewOption; const factory SelectOptionEditorEvent.selectOption(String optionId) = _SelectOption; + const factory SelectOptionEditorEvent.unSelectOption(String optionId) = + _UnSelectOption; const factory SelectOptionEditorEvent.updateOption(SelectOptionPB option) = _UpdateOption; const factory SelectOptionEditorEvent.deleteOption(SelectOptionPB option) = _DeleteOption; + const factory SelectOptionEditorEvent.deleteAllOptions() = _DeleteAllOptions; const factory SelectOptionEditorEvent.filterOption(String optionName) = _SelectOptionFilter; const factory SelectOptionEditorEvent.trySelectOption(String optionName) = diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart index 22f3b02eb1..3bf8950b6c 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/select_option_service.dart @@ -21,11 +21,11 @@ class SelectOptionService { (result) { return result.fold( (option) { - final cellIdentifier = GridCellIdPB.create() + final cellIdentifier = CellPathPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; - final payload = SelectOptionChangesetPayloadPB.create() + final payload = SelectOptionChangesetPB.create() ..insertOptions.add(option) ..cellIdentifier = cellIdentifier; return GridEventUpdateSelectOption(payload).send(); @@ -39,24 +39,23 @@ class SelectOptionService { Future> update({ required SelectOptionPB option, }) { - final payload = SelectOptionChangesetPayloadPB.create() + final payload = SelectOptionChangesetPB.create() ..updateOptions.add(option) ..cellIdentifier = _cellIdentifier(); return GridEventUpdateSelectOption(payload).send(); } - Future> delete({ - required SelectOptionPB option, - }) { - final payload = SelectOptionChangesetPayloadPB.create() - ..deleteOptions.add(option) + Future> delete( + {required Iterable options}) { + final payload = SelectOptionChangesetPB.create() + ..deleteOptions.addAll(options) ..cellIdentifier = _cellIdentifier(); return GridEventUpdateSelectOption(payload).send(); } Future> getOptionContext() { - final payload = GridCellIdPB.create() + final payload = CellPathPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; @@ -66,7 +65,7 @@ class SelectOptionService { Future> select( {required Iterable optionIds}) { - final payload = SelectOptionCellChangesetPayloadPB.create() + final payload = SelectOptionCellChangesetPB.create() ..cellIdentifier = _cellIdentifier() ..insertOptionIds.addAll(optionIds); return GridEventUpdateSelectOptionCell(payload).send(); @@ -74,14 +73,14 @@ class SelectOptionService { Future> unSelect( {required Iterable optionIds}) { - final payload = SelectOptionCellChangesetPayloadPB.create() + final payload = SelectOptionCellChangesetPB.create() ..cellIdentifier = _cellIdentifier() ..deleteOptionIds.addAll(optionIds); return GridEventUpdateSelectOptionCell(payload).send(); } - GridCellIdPB _cellIdentifier() { - return GridCellIdPB.create() + CellPathPB _cellIdentifier() { + return CellPathPB.create() ..gridId = gridId ..fieldId = fieldId ..rowId = rowId; diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart index 3fa55b744f..86e3f93132 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/text_cell_bloc.dart @@ -35,7 +35,7 @@ class TextCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart index 824900f173..d2944e15e1 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_bloc.dart @@ -38,7 +38,7 @@ class URLCellBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart index 8e82c27f42..8cd1fadd91 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/cell/url_cell_editor_bloc.dart @@ -36,7 +36,7 @@ class URLCellEditorBloc extends Bloc { cellController.removeListener(_onCellChangedFn!); _onCellChangedFn = null; } - cellController.dispose(); + await cellController.dispose(); return super.close(); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart index 52b3d3368e..cd82ecf567 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_action_sheet_bloc.dart @@ -11,9 +11,16 @@ class FieldActionSheetBloc extends Bloc { final FieldService fieldService; - FieldActionSheetBloc({required FieldPB field, required this.fieldService}) - : super(FieldActionSheetState.initial( - FieldTypeOptionDataPB.create()..field_2 = field)) { + FieldActionSheetBloc({required GridFieldCellContext fieldCellContext}) + : fieldService = FieldService( + gridId: fieldCellContext.gridId, + fieldId: fieldCellContext.field.id, + ), + super( + FieldActionSheetState.initial( + TypeOptionPB.create()..field_2 = fieldCellContext.field, + ), + ) { on( (event, emit) async { await event.map( @@ -31,6 +38,13 @@ class FieldActionSheetBloc (err) => Log.error(err), ); }, + showField: (_ShowField value) async { + final result = await fieldService.updateField(visibility: true); + result.fold( + (l) => null, + (err) => Log.error(err), + ); + }, deleteField: (_DeleteField value) async { final result = await fieldService.deleteField(); result.fold( @@ -62,6 +76,7 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent { const factory FieldActionSheetEvent.updateFieldName(String name) = _UpdateFieldName; const factory FieldActionSheetEvent.hideField() = _HideField; + const factory FieldActionSheetEvent.showField() = _ShowField; const factory FieldActionSheetEvent.duplicateField() = _DuplicateField; const factory FieldActionSheetEvent.deleteField() = _DeleteField; const factory FieldActionSheetEvent.saveField() = _SaveField; @@ -70,12 +85,12 @@ class FieldActionSheetEvent with _$FieldActionSheetEvent { @freezed class FieldActionSheetState with _$FieldActionSheetState { const factory FieldActionSheetState({ - required FieldTypeOptionDataPB fieldTypeOptionData, + required TypeOptionPB fieldTypeOptionData, required String errorText, required String fieldName, }) = _FieldActionSheetState; - factory FieldActionSheetState.initial(FieldTypeOptionDataPB data) => + factory FieldActionSheetState.initial(TypeOptionPB data) => FieldActionSheetState( fieldTypeOptionData: data, errorText: '', diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart index 73127ed6df..b5cb8cd9bf 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_controller.dart @@ -27,7 +27,7 @@ class _GridFieldNotifier extends ChangeNotifier { List get fieldContexts => _fieldContexts; } -typedef OnChangeset = void Function(FieldChangesetPB); +typedef OnChangeset = void Function(GridFieldChangesetPB); typedef OnReceiveFields = void Function(List); class GridFieldController { @@ -247,7 +247,7 @@ class GridRowFieldNotifierImpl extends IGridRowFieldNotifier { @override void onRowFieldChanged(void Function(FieldPB) callback) { - _onChangesetFn = (FieldChangesetPB changeset) { + _onChangesetFn = (GridFieldChangesetPB changeset) { for (final updatedField in changeset.updatedFields) { callback(updatedField); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart index 1a4fe17405..0e8965a5a8 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_editor_bloc.dart @@ -57,6 +57,9 @@ class FieldEditorBloc extends Bloc { }, ); }, + switchToField: (FieldType fieldType) async { + await dataController.switchToField(fieldType); + }, ); }, ); @@ -73,6 +76,8 @@ class FieldEditorEvent with _$FieldEditorEvent { const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.updateName(String name) = _UpdateName; const factory FieldEditorEvent.deleteField() = _DeleteField; + const factory FieldEditorEvent.switchToField(FieldType fieldType) = + _SwitchToField; const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) = _DidReceiveFieldChanged; } diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart index b1bb885ae2..991a2b25cb 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_listener.dart @@ -27,11 +27,11 @@ class SingleFieldListener { } void _handler( - GridNotification ty, + GridDartNotification ty, Either result, ) { switch (ty) { - case GridNotification.DidUpdateField: + case GridDartNotification.DidUpdateField: result.fold( (payload) => _updateFieldNotifier?.value = left(FieldPB.fromBuffer(payload)), diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart index e7487b6f02..f29186e55a 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_service.dart @@ -36,7 +36,7 @@ class FieldService { double? width, List? typeOptionData, }) { - var payload = FieldChangesetPayloadPB.create() + var payload = FieldChangesetPB.create() ..gridId = gridId ..fieldId = fieldId; @@ -72,7 +72,7 @@ class FieldService { required String fieldId, required List typeOptionData, }) { - var payload = UpdateFieldTypeOptionPayloadPB.create() + var payload = TypeOptionChangesetPB.create() ..gridId = gridId ..fieldId = fieldId ..typeOptionData = typeOptionData; @@ -96,10 +96,10 @@ class FieldService { return GridEventDuplicateField(payload).send(); } - Future> getFieldTypeOptionData({ + Future> getFieldTypeOptionData({ required FieldType fieldType, }) { - final payload = FieldTypeOptionIdPB.create() + final payload = TypeOptionPathPB.create() ..gridId = gridId ..fieldId = fieldId ..fieldType = fieldType; diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart index 254a371654..1835ba6262 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/field_type_option_edit_bloc.dart @@ -25,6 +25,9 @@ class FieldTypeOptionEditBloc didReceiveFieldUpdated: (field) { emit(state.copyWith(field: field)); }, + switchToField: (FieldType fieldType) async { + await _dataController.switchToField(fieldType); + }, ); }, ); @@ -42,6 +45,8 @@ class FieldTypeOptionEditBloc @freezed class FieldTypeOptionEditEvent with _$FieldTypeOptionEditEvent { const factory FieldTypeOptionEditEvent.initial() = _Initial; + const factory FieldTypeOptionEditEvent.switchToField(FieldType fieldType) = + _SwitchToField; const factory FieldTypeOptionEditEvent.didReceiveFieldUpdated(FieldPB field) = _DidReceiveFieldUpdated; } @@ -53,8 +58,9 @@ class FieldTypeOptionEditState with _$FieldTypeOptionEditState { }) = _FieldTypeOptionEditState; factory FieldTypeOptionEditState.initial( - TypeOptionDataController fieldContext) => + TypeOptionDataController typeOptionDataController, + ) => FieldTypeOptionEditState( - field: fieldContext.field, + field: typeOptionDataController.field, ); } diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart index 61d931e43e..fd26cd3dd8 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/grid_listener.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; -typedef UpdateFieldNotifiedValue = Either; +typedef UpdateFieldNotifiedValue = Either; class GridFieldsListener { final String gridId; @@ -25,12 +25,12 @@ class GridFieldsListener { ); } - void _handler(GridNotification ty, Either result) { + void _handler(GridDartNotification ty, Either result) { switch (ty) { - case GridNotification.DidUpdateGridField: + case GridDartNotification.DidUpdateGridField: result.fold( (payload) => updateFieldsNotifier?.value = - left(FieldChangesetPB.fromBuffer(payload)), + left(GridFieldChangesetPB.fromBuffer(payload)), (error) => updateFieldsNotifier?.value = right(error), ); break; diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart index 327edf9def..0c6586244a 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_context.dart @@ -143,11 +143,11 @@ abstract class TypeOptionFieldDelegate { abstract class IFieldTypeOptionLoader { String get gridId; - Future> load(); + Future> load(); Future> switchToField( String fieldId, FieldType fieldType) { - final payload = EditFieldPayloadPB.create() + final payload = EditFieldChangesetPB.create() ..gridId = gridId ..fieldId = fieldId ..fieldType = fieldType; @@ -156,23 +156,46 @@ abstract class IFieldTypeOptionLoader { } } +/// Uses when creating a new field class NewFieldTypeOptionLoader extends IFieldTypeOptionLoader { + TypeOptionPB? fieldTypeOption; + @override final String gridId; NewFieldTypeOptionLoader({ required this.gridId, }); + /// Creates the field type option if the fieldTypeOption is null. + /// Otherwise, it loads the type option data from the backend. @override - Future> load() { - final payload = CreateFieldPayloadPB.create() - ..gridId = gridId - ..fieldType = FieldType.RichText; + Future> load() { + if (fieldTypeOption != null) { + final payload = TypeOptionPathPB.create() + ..gridId = gridId + ..fieldId = fieldTypeOption!.field_2.id + ..fieldType = fieldTypeOption!.field_2.fieldType; - return GridEventCreateFieldTypeOption(payload).send(); + return GridEventGetFieldTypeOption(payload).send(); + } else { + final payload = CreateFieldPayloadPB.create() + ..gridId = gridId + ..fieldType = FieldType.RichText; + + return GridEventCreateFieldTypeOption(payload).send().then((result) { + return result.fold( + (newFieldTypeOption) { + fieldTypeOption = newFieldTypeOption; + return left(newFieldTypeOption); + }, + (err) => right(err), + ); + }); + } } } +/// Uses when editing a existing field class FieldTypeOptionLoader extends IFieldTypeOptionLoader { @override final String gridId; @@ -184,8 +207,8 @@ class FieldTypeOptionLoader extends IFieldTypeOptionLoader { }); @override - Future> load() { - final payload = FieldTypeOptionIdPB.create() + Future> load() { + final payload = TypeOptionPathPB.create() ..gridId = gridId ..fieldId = field.id ..fieldType = field.fieldType; diff --git a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart index d11a35e764..621114045c 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/field/type_option/type_option_data_controller.dart @@ -12,16 +12,22 @@ import 'type_option_context.dart'; class TypeOptionDataController { final String gridId; final IFieldTypeOptionLoader loader; - late FieldTypeOptionDataPB _data; + late TypeOptionPB _data; final PublishNotifier _fieldNotifier = PublishNotifier(); + /// Returns a [TypeOptionDataController] used to modify the specified + /// [FieldPB]'s data + /// + /// Should call [loadTypeOptionData] if the passed-in [GridFieldContext] + /// is null + /// TypeOptionDataController({ required this.gridId, required this.loader, GridFieldContext? fieldContext, }) { if (fieldContext != null) { - _data = FieldTypeOptionDataPB.create() + _data = TypeOptionPB.create() ..gridId = gridId ..field_2 = fieldContext.field; } @@ -77,18 +83,17 @@ class TypeOptionDataController { ); } - Future switchToField(FieldType newFieldType) { - return loader.switchToField(field.id, newFieldType).then((result) { - return result.fold( - (_) { - // Should load the type-option data after switching to a new field. - // After loading the type-option data, the editor widget that uses - // the type-option data will be rebuild. - loadTypeOptionData(); - }, - (err) => Log.error(err), - ); - }); + Future switchToField(FieldType newFieldType) async { + final result = await loader.switchToField(field.id, newFieldType); + await result.fold( + (_) { + // Should load the type-option data after switching to a new field. + // After loading the type-option data, the editor widget that uses + // the type-option data will be rebuild. + loadTypeOptionData(); + }, + (err) => Future(() => Log.error(err)), + ); } void Function() addFieldListener(void Function(FieldPB) callback) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/filter/filter_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_bloc.dart new file mode 100644 index 0000000000..a98e40a3e0 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_bloc.dart @@ -0,0 +1,206 @@ +import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'dart:async'; +import 'filter_service.dart'; + +part 'filter_bloc.freezed.dart'; + +class GridFilterBloc extends Bloc { + final String viewId; + final FilterFFIService _ffiService; + final FilterListener _listener; + GridFilterBloc({required this.viewId}) + : _ffiService = FilterFFIService(viewId: viewId), + _listener = FilterListener(viewId: viewId), + super(GridFilterState.initial()) { + on( + (event, emit) async { + event.when( + initial: () async { + _startListening(); + await _loadFilters(); + }, + deleteFilter: ( + String fieldId, + String filterId, + FieldType fieldType, + ) { + _ffiService.deleteFilter( + fieldId: fieldId, + filterId: filterId, + fieldType: fieldType, + ); + }, + didReceiveFilters: (filters) { + emit(state.copyWith(filters: filters)); + }, + createCheckboxFilter: ( + String fieldId, + CheckboxFilterCondition condition, + ) { + _ffiService.createCheckboxFilter( + fieldId: fieldId, + condition: condition, + ); + }, + createNumberFilter: ( + String fieldId, + NumberFilterCondition condition, + String content, + ) { + _ffiService.createNumberFilter( + fieldId: fieldId, + condition: condition, + content: content, + ); + }, + createTextFilter: ( + String fieldId, + TextFilterCondition condition, + String content, + ) { + _ffiService.createTextFilter( + fieldId: fieldId, + condition: condition, + ); + }, + createDateFilter: ( + String fieldId, + DateFilterCondition condition, + int timestamp, + ) { + _ffiService.createDateFilter( + fieldId: fieldId, + condition: condition, + timestamp: timestamp, + ); + }, + createDateFilterInRange: ( + String fieldId, + DateFilterCondition condition, + int start, + int end, + ) { + _ffiService.createDateFilter( + fieldId: fieldId, + condition: condition, + start: start, + end: end, + ); + }, + ); + }, + ); + } + + void _startListening() { + _listener.start(onFilterChanged: (result) { + result.fold( + (changeset) { + final List filters = List.from(state.filters); + + // Deletes the filters + final deleteFilterIds = + changeset.deleteFilters.map((e) => e.id).toList(); + filters.retainWhere( + (element) => !deleteFilterIds.contains(element.id), + ); + + // Inserts the new filter if it's not exist + for (final newFilter in changeset.insertFilters) { + final index = + filters.indexWhere((element) => element.id == newFilter.id); + if (index == -1) { + filters.add(newFilter); + } + } + + if (!isClosed) { + add(GridFilterEvent.didReceiveFilters(filters)); + } + }, + (err) => Log.error(err), + ); + }); + } + + Future _loadFilters() async { + final result = await _ffiService.getAllFilters(); + result.fold( + (filters) { + if (!isClosed) { + add(GridFilterEvent.didReceiveFilters(filters)); + } + }, + (err) => Log.error(err), + ); + } + + @override + Future close() async { + await _listener.stop(); + return super.close(); + } +} + +@freezed +class GridFilterEvent with _$GridFilterEvent { + const factory GridFilterEvent.initial() = _Initial; + const factory GridFilterEvent.didReceiveFilters(List filters) = + _DidReceiveFilters; + + const factory GridFilterEvent.deleteFilter({ + required String fieldId, + required String filterId, + required FieldType fieldType, + }) = _DeleteFilter; + + const factory GridFilterEvent.createTextFilter({ + required String fieldId, + required TextFilterCondition condition, + required String content, + }) = _CreateTextFilter; + + const factory GridFilterEvent.createCheckboxFilter({ + required String fieldId, + required CheckboxFilterCondition condition, + }) = _CreateCheckboxFilter; + + const factory GridFilterEvent.createNumberFilter({ + required String fieldId, + required NumberFilterCondition condition, + required String content, + }) = _CreateCheckboxFitler; + + const factory GridFilterEvent.createDateFilter({ + required String fieldId, + required DateFilterCondition condition, + required int start, + }) = _CreateDateFitler; + + const factory GridFilterEvent.createDateFilterInRange({ + required String fieldId, + required DateFilterCondition condition, + required int start, + required int end, + }) = _CreateDateFitlerInRange; +} + +@freezed +class GridFilterState with _$GridFilterState { + const factory GridFilterState({ + required List filters, + }) = _GridFilterState; + + factory GridFilterState.initial() => const GridFilterState( + filters: [], + ); +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart new file mode 100644 index 0000000000..94fd5942d7 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart @@ -0,0 +1,53 @@ +import 'dart:typed_data'; + +import 'package:app_flowy/core/grid_notification.dart'; +import 'package:flowy_infra/notifier.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart'; +import 'package:dartz/dartz.dart'; + +typedef UpdateFilterNotifiedValue + = Either; + +class FilterListener { + final String viewId; + + PublishNotifier? _filterNotifier = + PublishNotifier(); + GridNotificationListener? _listener; + FilterListener({required this.viewId}); + + void start({ + required void Function(UpdateFilterNotifiedValue) onFilterChanged, + }) { + _filterNotifier?.addPublishListener(onFilterChanged); + _listener = GridNotificationListener( + objectId: viewId, + handler: _handler, + ); + } + + void _handler( + GridDartNotification ty, + Either result, + ) { + switch (ty) { + case GridDartNotification.DidUpdateFilter: + result.fold( + (payload) => _filterNotifier?.value = + left(FilterChangesetNotificationPB.fromBuffer(payload)), + (error) => _filterNotifier?.value = right(error), + ); + break; + default: + break; + } + } + + Future stop() async { + await _listener?.stop(); + _filterNotifier?.dispose(); + _filterNotifier = null; + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart new file mode 100644 index 0000000000..4cb703bcd3 --- /dev/null +++ b/frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart @@ -0,0 +1,202 @@ +import 'package:dartz/dartz.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart'; +import 'package:fixnum/fixnum.dart' as $fixnum; + +class FilterFFIService { + final String viewId; + const FilterFFIService({required this.viewId}); + + Future, FlowyError>> getAllFilters() { + final payload = GridIdPB()..value = viewId; + + return GridEventGetAllFilters(payload).send().then((result) { + return result.fold( + (repeated) => left(repeated.items), + (r) => right(r), + ); + }); + } + + Future> createTextFilter({ + required String fieldId, + required TextFilterCondition condition, + String content = "", + }) { + final filter = TextFilterPB() + ..condition = condition + ..content = content; + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.RichText, + data: filter.writeToBuffer(), + ); + } + + Future> createCheckboxFilter({ + required String fieldId, + required CheckboxFilterCondition condition, + }) { + final filter = CheckboxFilterPB()..condition = condition; + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.Checkbox, + data: filter.writeToBuffer(), + ); + } + + Future> createNumberFilter({ + required String fieldId, + required NumberFilterCondition condition, + String content = "", + }) { + final filter = NumberFilterPB() + ..condition = condition + ..content = content; + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.Number, + data: filter.writeToBuffer(), + ); + } + + Future> createDateFilter({ + required String fieldId, + required DateFilterCondition condition, + int? start, + int? end, + int? timestamp, + }) { + var filter = DateFilterPB(); + if (timestamp != null) { + filter.timestamp = $fixnum.Int64(timestamp); + } else { + if (start != null && end != null) { + filter.start = $fixnum.Int64(start); + filter.end = $fixnum.Int64(end); + } else { + throw Exception( + "Start and end should not be null if the timestamp is null"); + } + } + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.DateTime, + data: filter.writeToBuffer(), + ); + } + + Future> createURLFilter({ + required String fieldId, + required TextFilterCondition condition, + String content = "", + }) { + final filter = TextFilterPB() + ..condition = condition + ..content = content; + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.URL, + data: filter.writeToBuffer(), + ); + } + + Future> createSingleSelectFilter({ + required String fieldId, + required SelectOptionCondition condition, + List optionIds = const [], + }) { + final filter = SelectOptionFilterPB() + ..condition = condition + ..optionIds.addAll(optionIds); + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.SingleSelect, + data: filter.writeToBuffer(), + ); + } + + Future> createMultiSelectFilter({ + required String fieldId, + required SelectOptionCondition condition, + List optionIds = const [], + }) { + final filter = SelectOptionFilterPB() + ..condition = condition + ..optionIds.addAll(optionIds); + + return createFilter( + fieldId: fieldId, + fieldType: FieldType.MultiSelect, + data: filter.writeToBuffer(), + ); + } + + Future> createFilter({ + required String fieldId, + required FieldType fieldType, + required List data, + }) { + TextFilterCondition.DoesNotContain.value; + + final insertFilterPayload = CreateFilterPayloadPB.create() + ..fieldId = fieldId + ..fieldType = fieldType + ..data = data; + + final payload = GridSettingChangesetPB.create() + ..gridId = viewId + ..insertFilter = insertFilterPayload; + return GridEventUpdateGridSetting(payload).send().then((result) { + return result.fold( + (l) => left(l), + (err) { + Log.error(err); + return right(err); + }, + ); + }); + } + + Future> deleteFilter({ + required String fieldId, + required String filterId, + required FieldType fieldType, + }) { + TextFilterCondition.DoesNotContain.value; + + final deleteFilterPayload = DeleteFilterPayloadPB.create() + ..fieldId = fieldId + ..filterId = filterId + ..fieldType = fieldType; + + final payload = GridSettingChangesetPB.create() + ..gridId = viewId + ..deleteFilter = deleteFilterPayload; + return GridEventUpdateGridSetting(payload).send().then((result) { + return result.fold( + (l) => left(l), + (err) { + Log.error(err); + return right(err); + }, + ); + }); + } +} diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart index 597e686b69..fb3a9cc518 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_bloc.dart @@ -27,7 +27,7 @@ class GridBloc extends Bloc { await event.when( initial: () async { _startListening(); - await _loadGrid(emit); + await _openGrid(emit); }, createRow: () { state.loadingState.when( @@ -95,8 +95,8 @@ class GridBloc extends Bloc { ); } - Future _loadGrid(Emitter emit) async { - final result = await dataController.loadData(); + Future _openGrid(Emitter emit) async { + final result = await dataController.openGrid(); result.fold( (grid) { if (_createRowOperation != null) { diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart index aafaeb3774..08964de4f6 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_data_controller.dart @@ -65,8 +65,8 @@ class GridDataController { } // Loads the rows from each block - Future> loadData() async { - final result = await _gridFFIService.loadGrid(); + Future> openGrid() async { + final result = await _gridFFIService.openGrid(); return Future( () => result.fold( (grid) async { @@ -79,8 +79,8 @@ class GridDataController { ); } - void createRow() { - _gridFFIService.createRow(); + Future createRow() async { + await _gridFFIService.createRow(); } Future dispose() async { @@ -105,11 +105,9 @@ class GridDataController { fieldController: fieldController, ); - cache.addListener( - onRowsChanged: (reason) { - _onRowChanged?.call(rowInfos, reason); - }, - ); + cache.addListener(onRowsChanged: (reason) { + _onRowChanged?.call(rowInfos, reason); + }); _blocks[block.id] = cache; } diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart index c6c9b1fa5b..5f777eedf7 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_header_bloc.dart @@ -23,7 +23,13 @@ class GridHeaderBloc extends Bloc { _startListening(); }, didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) { - emit(state.copyWith(fields: value.fields)); + emit( + state.copyWith( + fields: value.fields + .where((element) => element.visibility) + .toList(), + ), + ); }, moveField: (_MoveField value) async { await _moveField(value, emit); diff --git a/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart b/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart index 40dd5eeda1..a903559093 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/grid_service.dart @@ -14,7 +14,7 @@ class GridFFIService { required this.gridId, }); - Future> loadGrid() async { + Future> openGrid() async { await FolderEventSetLatestView(ViewIdPB(value: gridId)).send(); final payload = GridIdPB(value: gridId); @@ -44,7 +44,7 @@ class GridFFIService { Future> getFields( {required List fieldIds}) { - final payload = QueryFieldPayloadPB.create() + final payload = GetFieldPayloadPB.create() ..gridId = gridId ..fieldIds = RepeatedFieldIdPB(items: fieldIds); return GridEventGetFields(payload).send(); diff --git a/frontend/app_flowy/lib/plugins/grid/application/prelude.dart b/frontend/app_flowy/lib/plugins/grid/application/prelude.dart index 7585c55e49..3affd4f7ff 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/prelude.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/prelude.dart @@ -15,7 +15,7 @@ export 'field/type_option/date_bloc.dart'; export 'field/type_option/number_bloc.dart'; export 'field/type_option/single_select_type_option.dart'; -// GridCellPB +// CellPB export 'cell/text_cell_bloc.dart'; export 'cell/number_cell_bloc.dart'; export 'cell/select_option_cell_bloc.dart'; diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart index 6e25742974..49bd7ccab3 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_cache.dart @@ -33,13 +33,18 @@ class GridRowCache { List _rowInfos = []; /// Use Map for faster access the raw row data. - final HashMap _rowByRowId; + final HashMap _rowInfoByRowId; final GridCellCache _cellCache; final IGridRowFieldNotifier _fieldNotifier; final _RowChangesetNotifier _rowChangeReasonNotifier; - UnmodifiableListView get rows => UnmodifiableListView(_rowInfos); + UnmodifiableListView get visibleRows { + var visibleRows = [..._rowInfos]; + visibleRows.retainWhere((element) => element.visible); + return UnmodifiableListView(visibleRows); + } + GridCellCache get cellCache => _cellCache; GridRowCache({ @@ -47,7 +52,7 @@ class GridRowCache { required this.block, required IGridRowFieldNotifier notifier, }) : _cellCache = GridCellCache(gridId: gridId), - _rowByRowId = HashMap(), + _rowInfoByRowId = HashMap(), _rowChangeReasonNotifier = _RowChangesetNotifier(), _fieldNotifier = notifier { // @@ -55,7 +60,12 @@ class GridRowCache { .receive(const RowsChangedReason.fieldDidChange())); notifier.onRowFieldChanged( (field) => _cellCache.removeCellWithFieldId(field.id)); - _rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList(); + + for (final row in block.rows) { + final rowInfo = buildGridRow(row); + _rowInfos.add(rowInfo); + _rowInfoByRowId[rowInfo.rowPB.id] = rowInfo; + } } Future dispose() async { @@ -64,14 +74,12 @@ class GridRowCache { await _cellCache.dispose(); } - void applyChangesets(List changesets) { - for (final changeset in changesets) { - _deleteRows(changeset.deletedRows); - _insertRows(changeset.insertedRows); - _updateRows(changeset.updatedRows); - _hideRows(changeset.hideRows); - _showRows(changeset.visibleRows); - } + void applyChangesets(GridBlockChangesetPB changeset) { + _deleteRows(changeset.deletedRows); + _insertRows(changeset.insertedRows); + _updateRows(changeset.updatedRows); + _hideRows(changeset.invisibleRows); + _showRows(changeset.visibleRows); } void _deleteRows(List deletedRows) { @@ -89,7 +97,7 @@ class GridRowCache { if (deletedRowByRowId[rowInfo.rowPB.id] == null) { newRows.add(rowInfo); } else { - _rowByRowId.remove(rowInfo.rowPB.id); + _rowInfoByRowId.remove(rowInfo.rowPB.id); deletedIndex.add(DeletedIndex(index: index, row: rowInfo)); } }); @@ -109,10 +117,9 @@ class GridRowCache { rowId: insertRow.row.id, ); insertIndexs.add(insertIndex); - _rowInfos.insert( - insertRow.index, - (buildGridRow(insertRow.row)), - ); + final rowInfo = buildGridRow(insertRow.row); + _rowInfos.insert(insertRow.index, rowInfo); + _rowInfoByRowId[rowInfo.rowPB.id] = rowInfo; } _rowChangeReasonNotifier.receive(RowsChangedReason.insert(insertIndexs)); @@ -130,10 +137,11 @@ class GridRowCache { (rowInfo) => rowInfo.rowPB.id == rowId, ); if (index != -1) { - _rowByRowId[rowId] = updatedRow; + final rowInfo = buildGridRow(updatedRow); + _rowInfoByRowId[rowId] = rowInfo; _rowInfos.removeAt(index); - _rowInfos.insert(index, buildGridRow(updatedRow)); + _rowInfos.insert(index, rowInfo); updatedIndexs[rowId] = UpdatedIndex(index: index, rowId: rowId); } } @@ -141,13 +149,28 @@ class GridRowCache { _rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs)); } - void _hideRows(List hideRows) {} + void _hideRows(List invisibleRows) { + for (final rowId in invisibleRows) { + _rowInfoByRowId[rowId]?.visible = false; + } - void _showRows(List visibleRows) {} + if (invisibleRows.isNotEmpty) { + _rowChangeReasonNotifier + .receive(const RowsChangedReason.filterDidChange()); + } + } - void onRowsChanged( - void Function(RowsChangedReason) onRowChanged, - ) { + void _showRows(List visibleRows) { + for (final rowId in visibleRows) { + _rowInfoByRowId[rowId]?.visible = true; + } + if (visibleRows.isNotEmpty) { + _rowChangeReasonNotifier + .receive(const RowsChangedReason.filterDidChange()); + } + } + + void onRowsChanged(void Function(RowsChangedReason) onRowChanged) { _rowChangeReasonNotifier.addListener(() { onRowChanged(_rowChangeReasonNotifier.reason); }); @@ -165,9 +188,10 @@ class GridRowCache { notifyUpdate() { if (onCellUpdated != null) { - final row = _rowByRowId[rowId]; - if (row != null) { - final GridCellMap cellDataMap = _makeGridCells(rowId, row); + final rowInfo = _rowInfoByRowId[rowId]; + if (rowInfo != null) { + final GridCellMap cellDataMap = + _makeGridCells(rowId, rowInfo.rowPB); onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason); } } @@ -190,7 +214,7 @@ class GridRowCache { } GridCellMap loadGridCells(String rowId) { - final RowPB? data = _rowByRowId[rowId]; + final RowPB? data = _rowInfoByRowId[rowId]?.rowPB; if (data == null) { _loadRow(rowId); } @@ -232,7 +256,6 @@ class GridRowCache { final updatedRow = optionRow.row; updatedRow.freeze(); - _rowByRowId[updatedRow.id] = updatedRow; final index = _rowInfos.indexWhere((rowInfo) => rowInfo.rowPB.id == updatedRow.id); if (index != -1) { @@ -240,6 +263,7 @@ class GridRowCache { if (_rowInfos[index].rowPB != updatedRow) { final rowInfo = _rowInfos.removeAt(index).copyWith(rowPB: updatedRow); _rowInfos.insert(index, rowInfo); + _rowInfoByRowId[rowInfo.rowPB.id] = rowInfo; // Calculate the update index final UpdatedIndexs updatedIndexs = UpdatedIndexs(); @@ -260,6 +284,7 @@ class GridRowCache { gridId: gridId, fields: _fieldNotifier.fields, rowPB: rowPB, + visible: true, ); } } @@ -277,16 +302,18 @@ class _RowChangesetNotifier extends ChangeNotifier { update: (_) => notifyListeners(), fieldDidChange: (_) => notifyListeners(), initial: (_) {}, + filterDidChange: (_FilterDidChange value) => notifyListeners(), ); } } -@freezed +@unfreezed class RowInfo with _$RowInfo { - const factory RowInfo({ + factory RowInfo({ required String gridId, required UnmodifiableListView fields, required RowPB rowPB, + required bool visible, }) = _RowInfo; } @@ -300,6 +327,7 @@ class RowsChangedReason with _$RowsChangedReason { const factory RowsChangedReason.delete(DeletedIndexs items) = _Delete; const factory RowsChangedReason.update(UpdatedIndexs indexs) = _Update; const factory RowsChangedReason.fieldDidChange() = _FieldDidChange; + const factory RowsChangedReason.filterDidChange() = _FilterDidChange; const factory RowsChangedReason.initial() = InitialListState; } diff --git a/frontend/app_flowy/lib/plugins/grid/application/row/row_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/row/row_listener.dart index 1df24c50c2..76ee622a41 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/row/row_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/row/row_listener.dart @@ -23,9 +23,9 @@ class RowListener { _listener = GridNotificationListener(objectId: rowId, handler: _handler); } - void _handler(GridNotification ty, Either result) { + void _handler(GridDartNotification ty, Either result) { switch (ty) { - case GridNotification.DidUpdateRow: + case GridDartNotification.DidUpdateRow: result.fold( (payload) => updateRowNotifier?.value = left(RowPB.fromBuffer(payload)), diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart index 8b58d5ea0c..a4341517ac 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/group_bloc.dart @@ -1,3 +1,4 @@ +import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -13,9 +14,10 @@ class GridGroupBloc extends Bloc { final SettingFFIService _settingFFIService; Function(List)? _onFieldsFn; - GridGroupBloc( - {required String viewId, required GridFieldController fieldController}) - : _fieldController = fieldController, + GridGroupBloc({ + required String viewId, + required GridFieldController fieldController, + }) : _fieldController = fieldController, _settingFFIService = SettingFFIService(viewId: viewId), super(GridGroupState.initial(viewId, fieldController.fieldContexts)) { on( @@ -27,11 +29,12 @@ class GridGroupBloc extends Bloc { didReceiveFieldUpdate: (fieldContexts) { emit(state.copyWith(fieldContexts: fieldContexts)); }, - setGroupByField: (String fieldId, FieldType fieldType) { - _settingFFIService.groupByField( + setGroupByField: (String fieldId, FieldType fieldType) async { + final result = await _settingFFIService.groupByField( fieldId: fieldId, fieldType: fieldType, ); + result.fold((l) => null, (err) => Log.error(err)); }, ); }, diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart index 00de48ca78..d48d85d516 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_listener.dart @@ -24,9 +24,9 @@ class SettingListener { _listener = GridNotificationListener(objectId: gridId, handler: _handler); } - void _handler(GridNotification ty, Either result) { + void _handler(GridDartNotification ty, Either result) { switch (ty) { - case GridNotification.DidUpdateGridSetting: + case GridDartNotification.DidUpdateGridSetting: result.fold( (payload) => _updateSettingNotifier?.value = left( GridSettingPB.fromBuffer(payload), diff --git a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart index b050809bb5..ac0a65b637 100644 --- a/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart +++ b/frontend/app_flowy/lib/plugins/grid/application/setting/setting_service.dart @@ -23,7 +23,7 @@ class SettingFFIService { final insertGroupPayload = InsertGroupPayloadPB.create() ..fieldId = fieldId ..fieldType = fieldType; - final payload = GridSettingChangesetPayloadPB.create() + final payload = GridSettingChangesetPB.create() ..gridId = viewId ..insertGroup = insertGroupPayload; diff --git a/frontend/app_flowy/lib/plugins/grid/grid.dart b/frontend/app_flowy/lib/plugins/grid/grid.dart index 91f6ca5063..fdce23af26 100644 --- a/frontend/app_flowy/lib/plugins/grid/grid.dart +++ b/frontend/app_flowy/lib/plugins/grid/grid.dart @@ -22,11 +22,14 @@ class GridPluginBuilder implements PluginBuilder { @override String get menuName => LocaleKeys.grid_menuName.tr(); + @override + String get menuIcon => "editor/grid"; + @override PluginType get pluginType => PluginType.grid; @override - ViewDataTypePB get dataType => ViewDataTypePB.Database; + ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat; @override ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Grid; diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart index 38a437dee3..2867fb4e2c 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/grid_page.dart @@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart' import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui_web.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; @@ -335,8 +334,6 @@ class RowCountBadge extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return BlocSelector( selector: (state) => state.rowCount, builder: (context, rowCount) { @@ -348,7 +345,7 @@ class RowCountBadge extends StatelessWidget { FlowyText.regular( '${LocaleKeys.grid_row_count.tr()} : ', fontSize: 13, - color: theme.shader3, + color: Theme.of(context).hintColor, ), FlowyText.regular(rowCount.toString(), fontSize: 13), ], diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart index 341d4095b0..ba2541f780 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_accessory.dart @@ -1,12 +1,14 @@ +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flowy_infra/size.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'cell_builder.dart'; @@ -69,12 +71,12 @@ class _PrimaryCellAccessoryState extends State if (widget.isCellEditing) { return const SizedBox(); } else { - final theme = context.watch(); return Tooltip( message: LocaleKeys.tooltip_openAsPage.tr(), + textStyle: TextStyles.caption.textColor(Colors.white), child: svgWidget( "grid/expander", - color: theme.main1, + color: Theme.of(context).colorScheme.primary, ), ); } @@ -181,13 +183,14 @@ class _Background extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Consumer( builder: (context, state, child) { if (state.onHover) { return FlowyHoverContainer( style: HoverStyle( - borderRadius: Corners.s6Border, hoverColor: theme.shader6), + borderRadius: Corners.s6Border, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + ), ); } else { return const SizedBox(); @@ -204,12 +207,11 @@ class CellAccessoryContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); final children = accessories.where((accessory) => accessory.enable()).map((accessory) { final hover = FlowyHover( style: - HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface), + HoverStyle(hoverColor: AFThemeExtension.of(context).lightGreyHover), builder: (_, onHover) => Container( width: 26, height: 26, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart index 43a150d082..5ab98f2931 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/cell_container.dart @@ -1,6 +1,4 @@ -import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -66,12 +64,17 @@ class CellContainer extends StatelessWidget { } BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) { - final theme = context.watch(); if (isFocus) { - final borderSide = BorderSide(color: theme.main1, width: 1.0); + final borderSide = BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.0, + ); return BoxDecoration(border: Border.fromBorderSide(borderSide)); } else { - final borderSide = BorderSide(color: theme.shader5, width: 1.0); + final borderSide = BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ); return BoxDecoration( border: Border(right: borderSide, bottom: borderSide)); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checkbox_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checkbox_cell.dart index e9bd8c79a4..ad1f8ad277 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checkbox_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/checkbox_cell.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../layout/sizes.dart'; import 'cell_builder.dart'; @@ -44,6 +44,7 @@ class _CheckboxCellState extends GridCellState { child: Padding( padding: GridSize.cellContentInsets, child: FlowyIconButton( + hoverColor: Colors.transparent, onPressed: () => context .read() .add(const CheckboxCellEvent.select()), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart index 3213adff81..fd35d4d198 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_cell.dart @@ -1,3 +1,4 @@ +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/widgets.dart'; @@ -68,7 +69,7 @@ class _DateCellState extends GridCellState { controller: _popover, triggerActions: PopoverTriggerFlags.none, direction: PopoverDirection.bottomWithLeftAligned, - constraints: BoxConstraints.loose(const Size(320, 500)), + constraints: BoxConstraints.loose(const Size(320, 520)), margin: EdgeInsets.zero, child: SizedBox.expand( child: GestureDetector( @@ -78,7 +79,10 @@ class _DateCellState extends GridCellState { alignment: alignment, child: Padding( padding: GridSize.cellContentInsets, - child: FlowyText.medium(state.dateStr, fontSize: 12), + child: FlowyText.medium( + state.dateStr, + fontSize: FontSizes.s14, + ), ), ), ), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart index 672e0e648e..822b989da9 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/date_cell/date_editor.dart @@ -6,8 +6,10 @@ import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dar import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show Either; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -20,6 +22,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:table_calendar/table_calendar.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import '../../../layout/sizes.dart'; import '../../header/type_option/date.dart'; @@ -110,19 +113,18 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocProvider.value( value: bloc, child: BlocBuilder( buildWhen: (p, c) => false, builder: (context, state) { List children = [ - _buildCalendar(theme, context), + _buildCalendar(context), _TimeTextField( bloc: context.read(), popoverMutex: popoverMutex, ), - Divider(height: 1, color: theme.shader5), + Divider(height: 1, color: Theme.of(context).dividerColor), const _IncludeTimeButton(), _DateTypeOptionButton(popoverMutex: popoverMutex) ]; @@ -150,7 +152,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { super.dispose(); } - Widget _buildCalendar(AppTheme theme, BuildContext context) { + Widget _buildCalendar(BuildContext context) { return BlocBuilder( builder: (context, state) { return TableCalendar( @@ -163,6 +165,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { headerStyle: HeaderStyle( formatButtonVisible: false, titleCentered: true, + titleTextStyle: TextStyles.body1.size(FontSizes.s14), leftChevronMargin: EdgeInsets.zero, leftChevronPadding: EdgeInsets.zero, leftChevronIcon: svgWidget("home/arrow_left"), @@ -174,49 +177,56 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> { daysOfWeekStyle: DaysOfWeekStyle( dowTextFormatter: (date, locale) => DateFormat.E(locale).format(date).toUpperCase(), - weekdayStyle: TextStyle( + weekdayStyle: TextStyles.general( fontSize: 13, - color: theme.shader3, + fontWeight: FontWeight.w400, + color: Theme.of(context).hintColor, ), - weekendStyle: TextStyle( + weekendStyle: TextStyles.general( fontSize: 13, - color: theme.shader3, + fontWeight: FontWeight.w400, + color: Theme.of(context).hintColor, ), ), calendarStyle: CalendarStyle( cellMargin: const EdgeInsets.all(3), defaultDecoration: BoxDecoration( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, shape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(6)), ), selectedDecoration: BoxDecoration( - color: theme.main1, + color: Theme.of(context).colorScheme.primary, shape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(6)), ), todayDecoration: BoxDecoration( - color: theme.shader4, + color: AFThemeExtension.of(context).lightGreyHover, shape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(6)), ), weekendDecoration: BoxDecoration( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, shape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(6)), ), outsideDecoration: BoxDecoration( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, shape: BoxShape.rectangle, borderRadius: const BorderRadius.all(Radius.circular(6)), ), - selectedTextStyle: TextStyle( - color: theme.surface, - fontSize: 14.0, + defaultTextStyle: TextStyles.body1.size(FontSizes.s14), + weekendTextStyle: TextStyles.body1.size(FontSizes.s14), + selectedTextStyle: TextStyles.general( + fontSize: FontSizes.s14, + color: Theme.of(context).colorScheme.surface, ), - todayTextStyle: TextStyle( - color: theme.surface, - fontSize: 14.0, + todayTextStyle: TextStyles.general( + fontSize: FontSizes.s14, + ), + outsideTextStyle: TextStyles.general( + fontSize: FontSizes.s14, + color: Theme.of(context).disabledColor, ), ), selectedDayPredicate: (day) { @@ -249,7 +259,6 @@ class _IncludeTimeButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocSelector( selector: (state) => state.dateTypeOptionPB.includeTime, builder: (context, includeTime) { @@ -259,17 +268,22 @@ class _IncludeTimeButton extends StatelessWidget { padding: kMargin, child: Row( children: [ - svgWidget("grid/clock", color: theme.iconColor), + svgWidget( + "grid/clock", + color: Theme.of(context).colorScheme.onSurface, + ), const HSpace(4), - FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(), - fontSize: 14), + FlowyText.medium( + LocaleKeys.grid_field_includeTime.tr(), + fontSize: FontSizes.s14, + ), const Spacer(), Toggle( value: includeTime, onChanged: (value) => context .read() .add(DateCalEvent.setIncludeTime(!value)), - style: ToggleStyle.big(theme), + style: ToggleStyle.big, padding: EdgeInsets.zero, ), ], @@ -324,7 +338,6 @@ class _TimeTextFieldState extends State<_TimeTextField> { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocConsumer( listener: (context, state) { _controller.text = state.time ?? ""; @@ -340,11 +353,11 @@ class _TimeTextFieldState extends State<_TimeTextField> { autoFocus: true, hintText: state.timeHintText, controller: _controller, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - focusBorderColor: theme.main1, - cursorColor: theme.main1, + style: TextStyles.body1.size(FontSizes.s14), + normalBorderColor: Theme.of(context).colorScheme.outline, + errorBorderColor: Theme.of(context).colorScheme.error, + focusBorderColor: Theme.of(context).colorScheme.primary, + cursorColor: Theme.of(context).colorScheme.primary, errorText: state.timeFormatError.fold(() => "", (error) => error), onEditingComplete: (value) { widget.bloc.add(DateCalEvent.setTime(value)); @@ -374,7 +387,6 @@ class _DateTypeOptionButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); final title = "${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}"; return BlocSelector( @@ -387,9 +399,11 @@ class _DateTypeOptionButton extends StatelessWidget { constraints: BoxConstraints.loose(const Size(140, 100)), child: FlowyButton( text: FlowyText.medium(title, fontSize: 14), - hoverColor: theme.hover, margin: kMargin, - rightIcon: svgWidget("grid/more", color: theme.iconColor), + rightIcon: svgWidget( + "grid/more", + color: Theme.of(context).colorScheme.onSurface, + ), ), popupBuilder: (BuildContext popContext) { return _CalDateTimeSetting( diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart index 83ba4270da..667d7b6c93 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/number_cell.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import '../../layout/sizes.dart'; import 'cell_builder.dart'; @@ -54,7 +57,7 @@ class _NumberCellState extends GridFocusNodeCellState { onEditingComplete: () => focusNode.unfocus(), onSubmitted: (_) => focusNode.unfocus(), maxLines: 1, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), textInputAction: TextInputAction.done, decoration: const InputDecoration( contentPadding: EdgeInsets.zero, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart index 90aa59d23a..90aac52592 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart @@ -1,34 +1,32 @@ -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; extension SelectOptionColorExtension on SelectOptionColorPB { Color make(BuildContext context) { - final theme = context.watch(); switch (this) { case SelectOptionColorPB.Purple: - return theme.tint1; + return AFThemeExtension.tint1; case SelectOptionColorPB.Pink: - return theme.tint2; + return AFThemeExtension.tint2; case SelectOptionColorPB.LightPink: - return theme.tint3; + return AFThemeExtension.tint3; case SelectOptionColorPB.Orange: - return theme.tint4; + return AFThemeExtension.tint4; case SelectOptionColorPB.Yellow: - return theme.tint5; + return AFThemeExtension.tint5; case SelectOptionColorPB.Lime: - return theme.tint6; + return AFThemeExtension.tint6; case SelectOptionColorPB.Green: - return theme.tint7; + return AFThemeExtension.tint7; case SelectOptionColorPB.Aqua: - return theme.tint8; + return AFThemeExtension.tint8; case SelectOptionColorPB.Blue: - return theme.tint9; + return AFThemeExtension.tint9; default: throw ArgumentError; } @@ -118,12 +116,10 @@ class SelectOptionTagCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Stack( fit: StackFit.expand, children: [ FlowyHover( - style: HoverStyle(hoverColor: theme.hover), child: InkWell( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 3), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart index 4d4b3ebb29..9d721f259a 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_cell.dart @@ -2,7 +2,6 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:appflowy_popover/appflowy_popover.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'; // ignore: unused_import @@ -164,8 +163,7 @@ class _SelectOptionWrapState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); - Widget child = _buildOptions(theme, context); + Widget child = _buildOptions(context); return Stack( alignment: AlignmentDirectional.center, @@ -203,13 +201,13 @@ class _SelectOptionWrapState extends State { ); } - Widget _buildOptions(AppTheme theme, BuildContext context) { + Widget _buildOptions(BuildContext context) { final Widget child; if (widget.selectOptions.isEmpty && widget.cellStyle != null) { child = FlowyText.medium( widget.cellStyle!.placeholder, fontSize: 14, - color: theme.shader3, + color: Theme.of(context).hintColor, ); } else { final children = widget.selectOptions.map( diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart index d6b08e507d..06fc419460 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart @@ -2,9 +2,9 @@ import 'dart:collection'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; @@ -53,20 +53,23 @@ class _SelectOptionCellEditorState extends State { )..add(const SelectOptionEditorEvent.initial()), child: BlocBuilder( builder: (context, state) { - return CustomScrollView( - shrinkWrap: true, - slivers: [ - SliverToBoxAdapter( - child: _TextField(popoverMutex: popoverMutex), - ), - const SliverToBoxAdapter(child: VSpace(6)), - const SliverToBoxAdapter(child: TypeOptionSeparator()), - const SliverToBoxAdapter(child: VSpace(6)), - const SliverToBoxAdapter(child: _Title()), - SliverToBoxAdapter( - child: _OptionList(popoverMutex: popoverMutex), - ), - ], + return Padding( + padding: const EdgeInsets.all(6.0), + child: CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverToBoxAdapter( + child: _TextField(popoverMutex: popoverMutex), + ), + const SliverToBoxAdapter(child: VSpace(6)), + const SliverToBoxAdapter(child: TypeOptionSeparator()), + const SliverToBoxAdapter(child: VSpace(6)), + const SliverToBoxAdapter(child: _Title()), + SliverToBoxAdapter( + child: _OptionList(popoverMutex: popoverMutex), + ), + ], + ), ); }, ), @@ -181,7 +184,6 @@ class _Title extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, child: Padding( @@ -189,7 +191,7 @@ class _Title extends StatelessWidget { child: FlowyText.medium( LocaleKeys.grid_selectOption_panelTitle.tr(), fontSize: 12, - color: theme.shader3, + color: Theme.of(context).hintColor, ), ), ); @@ -202,18 +204,17 @@ class _CreateOptionCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Row( children: [ FlowyText.medium( LocaleKeys.grid_selectOption_create.tr(), fontSize: 12, - color: theme.shader3, + color: Theme.of(context).hintColor, ), const HSpace(10), SelectOptionTag( name: name, - color: theme.shader6, + color: AFThemeExtension.of(context).lightGreyHover, onSelected: () => context .read() .add(SelectOptionEditorEvent.newOption(name)), @@ -249,7 +250,6 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { @override Widget build(BuildContext context) { - final theme = context.watch(); return AppFlowyPopover( controller: _popoverController, offset: const Offset(20, 0), @@ -261,9 +261,15 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { child: SelectOptionTagCell( option: widget.option, onSelected: (option) { - context - .read() - .add(SelectOptionEditorEvent.selectOption(option.id)); + if (widget.isSelected) { + context + .read() + .add(SelectOptionEditorEvent.unSelectOption(option.id)); + } else { + context + .read() + .add(SelectOptionEditorEvent.selectOption(option.id)); + } }, children: [ if (widget.isSelected) @@ -274,8 +280,12 @@ class _SelectOptionCellState extends State<_SelectOptionCell> { FlowyIconButton( width: 30, onPressed: () => _popoverController.show(), + hoverColor: Colors.transparent, iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("editor/details", color: theme.iconColor), + icon: svgWidget( + "editor/details", + color: Theme.of(context).colorScheme.onSurface, + ), ), ], ), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart index 9fd16c8f5d..a67a197c8d 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart @@ -1,15 +1,15 @@ import 'dart:collection'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:textfield_tags/textfield_tags.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'extension.dart'; @@ -63,8 +63,6 @@ class _SelectOptionTextFieldState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); - return TextFieldTags( textEditingController: controller, textfieldTagsController: widget.tagController, @@ -96,7 +94,7 @@ class _SelectOptionTextFieldState extends State { } if (text.isNotEmpty) { - widget.onSubmitted(text); + widget.onSubmitted(text.trim()); focusNode.requestFocus(); } }, @@ -104,10 +102,13 @@ class _SelectOptionTextFieldState extends State { maxLength: widget.maxLength, maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), decoration: InputDecoration( enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: theme.main1, width: 1.0), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.0, + ), borderRadius: Corners.s10Border, ), isDense: true, @@ -116,7 +117,10 @@ class _SelectOptionTextFieldState extends State { prefixIconConstraints: BoxConstraints(maxWidth: widget.distanceToText), focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: theme.main1, width: 1.0), + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + width: 1.0, + ), borderRadius: Corners.s10Border, ), ), @@ -132,32 +136,12 @@ class _SelectOptionTextFieldState extends State { return; } - final trimmedText = text.trim(); - List splits = []; - String currentString = ''; + final result = splitInput(text.trimLeft(), widget.textSeparators); - // split the string into tokens - for (final char in trimmedText.split('')) { - if (!widget.textSeparators.contains(char)) { - currentString += char; - continue; - } - if (currentString.isNotEmpty) { - splits.add(currentString); - } - currentString = ''; - } - // add the remainder (might be '') - splits.add(currentString); - - final submittedOptions = - splits.sublist(0, splits.length - 1).map((e) => e.trim()).toList(); - - final remainder = splits.elementAt(splits.length - 1).trimLeft(); - editingController.text = remainder; + editingController.text = result[1]; editingController.selection = TextSelection.collapsed(offset: controller.text.length); - widget.onPaste(submittedOptions, remainder); + widget.onPaste(result[0], result[1]); } Widget? _renderTags(BuildContext context, ScrollController sc) { @@ -190,3 +174,28 @@ class _SelectOptionTextFieldState extends State { ); } } + +@visibleForTesting +List splitInput(String input, List textSeparators) { + List splits = []; + String currentString = ''; + + // split the string into tokens + for (final char in input.split('')) { + if (textSeparators.contains(char)) { + if (currentString.trim().isNotEmpty) { + splits.add(currentString.trim()); + } + currentString = ''; + continue; + } + currentString += char; + } + // add the remainder (might be '') + splits.add(currentString); + + final submittedOptions = splits.sublist(0, splits.length - 1).toList(); + final remainder = splits.elementAt(splits.length - 1).trimLeft(); + + return [submittedOptions, remainder]; +} diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart index cf418c6e93..329afb3d86 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/text_cell.dart @@ -1,9 +1,13 @@ import 'dart:async'; +import 'package:app_flowy/plugins/grid/presentation/widgets/cell/prelude.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; import '../../layout/sizes.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'cell_builder.dart'; class GridTextCellStyle extends GridCellStyle { @@ -58,16 +62,22 @@ class _GridTextCellState extends GridFocusNodeCellState { } }, child: Padding( - padding: GridSize.cellContentInsets, + padding: EdgeInsets.only( + left: GridSize.cellContentInsets.left, + right: GridSize.cellContentInsets.right, + ), child: TextField( controller: _controller, focusNode: focusNode, onChanged: (value) => focusChanged(), onEditingComplete: () => focusNode.unfocus(), maxLines: null, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), decoration: InputDecoration( - contentPadding: EdgeInsets.zero, + contentPadding: EdgeInsets.only( + top: GridSize.cellContentInsets.top, + bottom: GridSize.cellContentInsets.bottom, + ), border: InputBorder.none, hintText: widget.cellStyle?.placeholder, isDense: true, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart index 17a899f3a1..58c63a309f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/cell_editor.dart @@ -1,9 +1,12 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/url_cell_editor_bloc.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class URLCellEditor extends StatefulWidget { final GridURLCellController cellController; @@ -42,7 +45,7 @@ class _URLCellEditorState extends State { controller: _controller, onChanged: (value) => focusChanged(), maxLines: null, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), decoration: const InputDecoration( contentPadding: EdgeInsets.zero, border: InputBorder.none, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart index fbf626802e..02c3ff8d6f 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/cell/url_cell/url_cell.dart @@ -5,12 +5,14 @@ import 'package:app_flowy/workspace/presentation/home/toast.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:url_launcher/url_launcher.dart'; import '../../../layout/sizes.dart'; import '../cell_accessory.dart'; @@ -111,7 +113,6 @@ class _GridURLCellState extends GridCellState { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocProvider.value( value: _cellBloc, child: BlocBuilder( @@ -122,11 +123,10 @@ class _GridURLCellState extends GridCellState { textAlign: TextAlign.left, text: TextSpan( text: state.content, - style: TextStyle( - color: theme.main2, - fontSize: 14, - decoration: TextDecoration.underline, - ), + style: TextStyles.general( + fontSize: FontSizes.s14, + color: Theme.of(context).colorScheme.primaryContainer, + ).underline, ), ), ); @@ -214,13 +214,15 @@ class _EditURLAccessoryState extends State<_EditURLAccessory> @override Widget build(BuildContext context) { - final theme = context.watch(); return AppFlowyPopover( constraints: BoxConstraints.loose(const Size(300, 160)), controller: _popoverController, direction: PopoverDirection.bottomWithLeftAligned, offset: const Offset(0, 20), - child: svgWidget("editor/edit", color: theme.iconColor), + child: svgWidget( + "editor/edit", + color: Theme.of(context).colorScheme.onSurface, + ), popupBuilder: (BuildContext popoverContext) { return URLEditorPopover( cellController: @@ -249,8 +251,10 @@ class _CopyURLAccessoryState extends State<_CopyURLAccessory> with GridCellAccessoryState { @override Widget build(BuildContext context) { - final theme = context.watch(); - return svgWidget("editor/copy", color: theme.iconColor); + return svgWidget( + "editor/copy", + color: Theme.of(context).colorScheme.onSurface, + ); } @override diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/common/text_field.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/common/text_field.dart index bd59b8b82c..728dceb9e4 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/common/text_field.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/common/text_field.dart @@ -1,7 +1,8 @@ -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class InputTextField extends StatefulWidget { final void Function(String)? onDone; @@ -10,6 +11,7 @@ class InputTextField extends StatefulWidget { final bool autoClearWhenDone; final String text; final int? maxLength; + final FocusNode? focusNode; const InputTextField({ required this.text, @@ -18,6 +20,7 @@ class InputTextField extends StatefulWidget { this.onChanged, this.autoClearWhenDone = false, this.maxLength, + this.focusNode, Key? key, }) : super(key: key); @@ -32,8 +35,11 @@ class _InputTextFieldState extends State { @override void initState() { - _focusNode = FocusNode(); + _focusNode = widget.focusNode ?? FocusNode(); _controller = TextEditingController(text: widget.text); + SchedulerBinding.instance.addPostFrameCallback((Duration _) { + _focusNode.requestFocus(); + }); _focusNode.addListener(notifyDidEndEditing); super.initState(); @@ -41,8 +47,6 @@ class _InputTextFieldState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); - final height = widget.maxLength == null ? 36.0 : 56.0; return RoundedInputField( @@ -51,10 +55,7 @@ class _InputTextFieldState extends State { autoFocus: true, height: height, maxLength: widget.maxLength, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), - normalBorderColor: theme.shader4, - focusBorderColor: theme.main1, - cursorColor: theme.main1, + style: TextStyles.body1.size(13), onChanged: (text) { if (widget.onChanged != null) { widget.onChanged!(text); @@ -75,7 +76,10 @@ class _InputTextFieldState extends State { @override void dispose() { _focusNode.removeListener(notifyDidEndEditing); - _focusNode.dispose(); + // only dispose the focusNode if it was created in this widget's initState + if (widget.focusNode == null) { + _focusNode.dispose(); + } super.dispose(); } @@ -97,12 +101,11 @@ class TypeOptionSeparator extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Container( - color: theme.shader4, - height: 0.25, + color: Theme.of(context).dividerColor, + height: 1.0, ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/footer/grid_footer.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/footer/grid_footer.dart index 338ada57fe..9d1f98346d 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/footer/grid_footer.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/footer/grid_footer.dart @@ -1,8 +1,8 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; @@ -13,12 +13,14 @@ class GridAddRowButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return FlowyButton( text: FlowyText.medium(LocaleKeys.grid_row_newRow.tr(), fontSize: 12), - hoverColor: theme.shader6, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: () => context.read().add(const GridEvent.createRow()), - leftIcon: svgWidget("home/add", color: theme.iconColor), + leftIcon: svgWidget( + "home/add", + color: Theme.of(context).colorScheme.onSurface, + ), ); } } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart index 0ee7462e22..270cdf17af 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell.dart @@ -1,8 +1,8 @@ import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart'; import 'package:app_flowy/plugins/grid/application/field/field_service.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; @@ -91,8 +91,10 @@ class _GridHeaderCellContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - final borderSide = BorderSide(color: theme.shader5, width: 1.0); + final borderSide = BorderSide( + color: Theme.of(context).dividerColor, + width: 1.0, + ); final decoration = BoxDecoration( border: Border( top: borderSide, @@ -116,8 +118,6 @@ class _DragToExpandLine extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return InkWell( onTap: () {}, child: GestureDetector( @@ -136,7 +136,7 @@ class _DragToExpandLine extends StatelessWidget { child: FlowyHover( cursor: SystemMouseCursors.resizeLeftRight, style: HoverStyle( - hoverColor: theme.main1, + hoverColor: Theme.of(context).colorScheme.primary, borderRadius: BorderRadius.zero, contentMargin: const EdgeInsets.only(left: 6), ), @@ -160,18 +160,18 @@ class FieldCellButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - // Using this technique to have proper text ellipsis // https://github.com/flutter/flutter/issues/18761#issuecomment-812390920 final text = Characters(field.name) .replaceAll(Characters(''), Characters('\u{200B}')) .toString(); return FlowyButton( - radius: BorderRadius.zero, - hoverColor: theme.shader6, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: onTap, - leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor), + leftIcon: svgWidget( + field.fieldType.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), text: FlowyText.medium( text, fontSize: 12, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart index 5dca885414..4b5830588e 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_cell_action_sheet.dart @@ -5,7 +5,6 @@ import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -72,8 +71,6 @@ class _EditFieldButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return BlocBuilder( builder: (context, state) { return SizedBox( @@ -83,7 +80,6 @@ class _EditFieldButton extends StatelessWidget { LocaleKeys.grid_field_editProperty.tr(), fontSize: 12, ), - hoverColor: theme.hover, onTap: onTap, ), ); @@ -151,14 +147,12 @@ class FieldActionCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return FlowyButton( text: FlowyText.medium( action.title(), fontSize: 12, - color: enable ? null : theme.shader4, + color: enable ? null : Theme.of(context).disabledColor, ), - hoverColor: theme.hover, onTap: () { if (enable) { action.run(context, fieldContext); @@ -167,7 +161,9 @@ class FieldActionCell extends StatelessWidget { }, leftIcon: svgWidget( action.iconName(), - color: enable ? theme.iconColor : theme.disableIconColor, + color: enable + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).disabledColor, ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart index 36bae16b47..98c0cd87b6 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_editor.dart @@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show none; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; @@ -20,8 +20,8 @@ class FieldEditor extends StatefulWidget { final String fieldName; final bool isGroupField; final Function(String)? onDeleted; - final IFieldTypeOptionLoader typeOptionLoader; + const FieldEditor({ required this.gridId, this.fieldName = "", @@ -165,7 +165,6 @@ class _FieldNameTextFieldState extends State<_FieldNameTextField> { @override Widget build(BuildContext context) { - final theme = context.watch(); return MultiBlocListener( listeners: [ BlocListener( @@ -188,12 +187,10 @@ class _FieldNameTextFieldState extends State<_FieldNameTextField> { return RoundedInputField( height: 36, focusNode: focusNode, - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500), + style: TextStyles.general( + fontSize: 13, + ), controller: controller, - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - focusBorderColor: theme.main1, - cursorColor: theme.main1, errorText: context.read().state.errorText, onChanged: (newName) { context @@ -219,7 +216,6 @@ class _DeleteFieldButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( buildWhen: (previous, current) => previous != current, builder: (context, state) { @@ -228,10 +224,9 @@ class _DeleteFieldButton extends StatelessWidget { text: FlowyText.medium( LocaleKeys.grid_field_delete.tr(), fontSize: 12, - color: enable ? null : theme.shader4, + color: enable ? null : Theme.of(context).disabledColor, ), onTap: () => onDeleted?.call(), - hoverColor: theme.hover, onHover: (_) => popoverMutex.close(), ); return SizedBox(height: 36, child: button); diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart index e504c0ab4f..ea4fe544a5 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_list.dart @@ -1,6 +1,5 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; @@ -10,7 +9,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import 'package:flutter/material.dart'; import '../../layout/sizes.dart'; import 'field_type_extension.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; typedef SelectFieldCallback = void Function(FieldType); @@ -60,15 +58,15 @@ class FieldTypeCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(fieldType.title(), fontSize: 12), - hoverColor: theme.hover, onTap: () => onSelectField(fieldType), - leftIcon: svgWidget(fieldType.iconName(), color: theme.iconColor), + leftIcon: svgWidget( + fieldType.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart index 97c144c491..a9b6d4114e 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/field_type_option_editor.dart @@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:dartz/dartz.dart' show Either; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -18,38 +17,42 @@ import 'field_type_list.dart'; import 'type_option/builder.dart'; typedef UpdateFieldCallback = void Function(FieldPB, Uint8List); -typedef SwitchToFieldCallback - = Future> Function( +typedef SwitchToFieldCallback = Future> + Function( String fieldId, FieldType fieldType, ); class FieldTypeOptionEditor extends StatelessWidget { - final TypeOptionDataController dataController; + final TypeOptionDataController _dataController; final PopoverMutex popoverMutex; const FieldTypeOptionEditor({ - required this.dataController, + required TypeOptionDataController dataController, required this.popoverMutex, Key? key, - }) : super(key: key); + }) : _dataController = dataController, + super(key: key); @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => FieldTypeOptionEditBloc(dataController) - ..add(const FieldTypeOptionEditEvent.initial()), + create: (context) { + final bloc = FieldTypeOptionEditBloc(_dataController); + bloc.add(const FieldTypeOptionEditEvent.initial()); + return bloc; + }, child: BlocBuilder( builder: (context, state) { - List children = [ - _switchFieldTypeButton(context, dataController.field) - ]; - final typeOptionWidget = - _typeOptionWidget(context: context, state: state); + final typeOptionWidget = _typeOptionWidget( + context: context, + state: state, + ); - if (typeOptionWidget != null) { - children.add(typeOptionWidget); - } + List children = [ + _SwitchFieldButton(popoverMutex: popoverMutex), + if (typeOptionWidget != null) typeOptionWidget + ]; return ListView( shrinkWrap: true, @@ -60,45 +63,69 @@ class FieldTypeOptionEditor extends StatelessWidget { ); } - Widget _switchFieldTypeButton(BuildContext context, FieldPB field) { - final theme = context.watch(); - return SizedBox( - height: GridSize.typeOptionItemHeight, - child: AppFlowyPopover( - constraints: BoxConstraints.loose(const Size(460, 540)), - asBarrier: true, - triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover, - mutex: popoverMutex, - offset: const Offset(20, 0), - popupBuilder: (context) { - return FieldTypeList(onSelectField: (newFieldType) { - dataController.switchToField(newFieldType); - }); - }, - child: FlowyButton( - text: FlowyText.medium(field.fieldType.title(), fontSize: 12), - margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - hoverColor: theme.hover, - leftIcon: - svgWidget(field.fieldType.iconName(), color: theme.iconColor), - rightIcon: svgWidget("grid/more", color: theme.iconColor), - ), - ), - ); - } - Widget? _typeOptionWidget({ required BuildContext context, required FieldTypeOptionEditState state, }) { return makeTypeOptionWidget( context: context, - dataController: dataController, + dataController: _dataController, popoverMutex: popoverMutex, ); } } +class _SwitchFieldButton extends StatelessWidget { + final PopoverMutex popoverMutex; + const _SwitchFieldButton({ + required this.popoverMutex, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final widget = AppFlowyPopover( + constraints: BoxConstraints.loose(const Size(460, 540)), + asBarrier: true, + triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover, + mutex: popoverMutex, + offset: const Offset(20, 0), + popupBuilder: (popOverContext) { + return FieldTypeList(onSelectField: (newFieldType) { + context + .read() + .add(FieldTypeOptionEditEvent.switchToField(newFieldType)); + }); + }, + child: _buildMoreButton(context), + ); + + return SizedBox( + height: GridSize.typeOptionItemHeight, + child: widget, + ); + } + + Widget _buildMoreButton(BuildContext context) { + final bloc = context.read(); + return FlowyButton( + text: FlowyText.medium( + bloc.state.field.fieldType.title(), + fontSize: 12, + ), + margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + leftIcon: svgWidget( + bloc.state.field.fieldType.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), + rightIcon: svgWidget( + "grid/more", + color: Theme.of(context).colorScheme.onSurface, + ), + ); + } +} + abstract class TypeOptionWidget extends StatelessWidget { const TypeOptionWidget({Key? key}) : super(key: key); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart index 735a179c1e..76ac94ac08 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/grid_header.dart @@ -5,8 +5,8 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -94,7 +94,6 @@ class _GridHeaderState extends State<_GridHeader> { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( buildWhen: (previous, current) => previous.fields != current.fields, builder: (context, state) { @@ -107,7 +106,7 @@ class _GridHeaderState extends State<_GridHeader> { .toList(); return Container( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, child: RepaintBoundary( child: ReorderableRow( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -154,8 +153,8 @@ class _CellTrailing extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - final borderSide = BorderSide(color: theme.shader4, width: 0.4); + final borderSide = + BorderSide(color: Theme.of(context).dividerColor, width: 1.0); return Container( width: GridSize.trailHeaderPadding, decoration: BoxDecoration( @@ -173,8 +172,6 @@ class CreateFieldButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return AppFlowyPopover( direction: PopoverDirection.bottomWithRightAligned, asBarrier: true, @@ -185,17 +182,16 @@ class CreateFieldButton extends StatelessWidget { LocaleKeys.grid_field_newColumn.tr(), fontSize: 12, ), - hoverColor: theme.shader6, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: () {}, leftIcon: svgWidget( "home/add", - color: theme.iconColor, + color: Theme.of(context).colorScheme.onSurface, ), ), popupBuilder: (BuildContext popover) { return FieldEditor( gridId: gridId, - fieldName: "", typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId), ); }, diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart index 954c933511..c5926bf1e2 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/date.dart @@ -5,7 +5,6 @@ import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dar import 'package:easy_localization/easy_localization.dart' hide DateFormat; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -15,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import '../../../layout/sizes.dart'; +import '../../common/text_field.dart'; import '../field_type_option_editor.dart'; import 'builder.dart'; @@ -53,11 +53,14 @@ class DateTypeOptionWidget extends TypeOptionWidget { listener: (context, state) => typeOptionContext.typeOption = state.typeOption, builder: (context, state) { - return Column(children: [ - _renderDateFormatButton(context, state.typeOption.dateFormat), - _renderTimeFormatButton(context, state.typeOption.timeFormat), - const _IncludeTimeButton(), - ]); + return Column( + children: [ + const TypeOptionSeparator(), + _renderDateFormatButton(context, state.typeOption.dateFormat), + _renderTimeFormatButton(context, state.typeOption.timeFormat), + const _IncludeTimeButton(), + ], + ); }, ), ); @@ -116,17 +119,18 @@ class DateFormatButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_dateFormat.tr(), fontSize: 12), margin: GridSize.typeOptionContentInsets, - hoverColor: theme.hover, onTap: onTap, onHover: onHover, - rightIcon: svgWidget("grid/more", color: theme.iconColor), + rightIcon: svgWidget( + "grid/more", + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } @@ -142,17 +146,18 @@ class TimeFormatButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_timeFormat.tr(), fontSize: 12), margin: GridSize.typeOptionContentInsets, - hoverColor: theme.hover, onTap: onTap, onHover: onHover, - rightIcon: svgWidget("grid/more", color: theme.iconColor), + rightIcon: svgWidget( + "grid/more", + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } @@ -163,7 +168,6 @@ class _IncludeTimeButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocSelector( selector: (state) => state.typeOption.includeTime, builder: (context, includeTime) { @@ -183,7 +187,7 @@ class _IncludeTimeButton extends StatelessWidget { .read() .add(DateTypeOptionEvent.includeTime(!value)); }, - style: ToggleStyle.big(theme), + style: ToggleStyle.big, padding: EdgeInsets.zero, ), ], @@ -242,7 +246,6 @@ class DateFormatCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); Widget? checkmark; if (isSelected) { checkmark = svgWidget("grid/checkmark"); @@ -252,7 +255,6 @@ class DateFormatCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(dateFormat.title(), fontSize: 12), - hoverColor: theme.hover, rightIcon: checkmark, onTap: () => onSelected(dateFormat), ), @@ -326,7 +328,6 @@ class TimeFormatCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); Widget? checkmark; if (isSelected) { checkmark = svgWidget("grid/checkmark"); @@ -336,7 +337,6 @@ class TimeFormatCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(timeFormat.title(), fontSize: 12), - hoverColor: theme.hover, rightIcon: checkmark, onTap: () => onSelected(timeFormat), ), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart index bf82aecabc..f5061e1204 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/number.dart @@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/number_form import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -31,7 +30,12 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder { ); @override - Widget? build(BuildContext context) => _widget; + Widget? build(BuildContext context) { + return Column(children: [ + const TypeOptionSeparator(), + _widget, + ]); + } } class NumberTypeOptionWidget extends TypeOptionWidget { @@ -45,7 +49,6 @@ class NumberTypeOptionWidget extends TypeOptionWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocProvider( create: (context) => NumberTypeOptionBloc(typeOptionContext: typeOptionContext), @@ -63,8 +66,10 @@ class NumberTypeOptionWidget extends TypeOptionWidget { constraints: BoxConstraints.loose(const Size(460, 440)), child: FlowyButton( margin: GridSize.typeOptionContentInsets, - hoverColor: theme.hover, - rightIcon: svgWidget("grid/more", color: theme.iconColor), + rightIcon: svgWidget( + "grid/more", + color: Theme.of(context).colorScheme.onSurface, + ), text: Row( children: [ FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(), @@ -160,7 +165,6 @@ class NumberFormatCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); Widget? checkmark; if (isSelected) { checkmark = svgWidget("grid/checkmark"); @@ -170,7 +174,6 @@ class NumberFormatCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(format.title(), fontSize: 12), - hoverColor: theme.hover, onTap: () => onSelected(format), rightIcon: checkmark, ), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart index 00d1ea6332..a44ed2db8c 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option.dart @@ -1,7 +1,6 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -45,9 +44,11 @@ class SelectOptionTypeOptionWidget extends StatelessWidget { const TypeOptionSeparator(), const OptionTitle(), if (state.isEditingOption) - const Padding( - padding: EdgeInsets.only(bottom: 10), - child: _CreateOptionTextField(), + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: _CreateOptionTextField( + popoverMutex: popoverMutex, + ), ), if (state.options.isEmpty && !state.isEditingOption) const _AddOptionButton(), @@ -69,9 +70,13 @@ class OptionTitle extends StatelessWidget { return BlocBuilder( builder: (context, state) { List children = [ - FlowyText.medium(LocaleKeys.grid_field_optionTitle.tr(), fontSize: 12) + FlowyText.medium( + LocaleKeys.grid_field_optionTitle.tr(), + fontSize: 12, + color: Theme.of(context).hintColor, + ) ]; - if (state.options.isNotEmpty) { + if (state.options.isNotEmpty && !state.isEditingOption) { children.add(const Spacer()); children.add(const _OptionTitleButton()); } @@ -90,7 +95,6 @@ class _OptionTitleButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( width: 100, height: 26, @@ -100,7 +104,6 @@ class _OptionTitleButton extends StatelessWidget { fontSize: 12, textAlign: TextAlign.center, ), - hoverColor: theme.hover, onTap: () { context .read() @@ -178,8 +181,6 @@ class _OptionCellState extends State<_OptionCell> { @override Widget build(BuildContext context) { - final theme = context.watch(); - return AppFlowyPopover( controller: _popoverController, mutex: widget.popoverMutex, @@ -196,7 +197,7 @@ class _OptionCellState extends State<_OptionCell> { children: [ svgWidget( "grid/details", - color: theme.iconColor, + color: Theme.of(context).colorScheme.onSurface, ), ], ), @@ -228,26 +229,54 @@ class _AddOptionButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_field_addSelectOption.tr(), fontSize: 12), - hoverColor: theme.hover, onTap: () { context .read() .add(const SelectOptionTypeOptionEvent.addingOption()); }, - leftIcon: svgWidget("home/add", color: theme.iconColor), + leftIcon: svgWidget( + "home/add", + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } } -class _CreateOptionTextField extends StatelessWidget { - const _CreateOptionTextField({Key? key}) : super(key: key); +class _CreateOptionTextField extends StatefulWidget { + final PopoverMutex? popoverMutex; + const _CreateOptionTextField({ + Key? key, + this.popoverMutex, + }) : super(key: key); + + @override + State<_CreateOptionTextField> createState() => _CreateOptionTextFieldState(); +} + +class _CreateOptionTextFieldState extends State<_CreateOptionTextField> { + late final FocusNode _focusNode; + + @override + void initState() { + _focusNode = FocusNode(); + _focusNode.addListener(() { + if (_focusNode.hasFocus) { + widget.popoverMutex?.close(); + } + }); + widget.popoverMutex?.listenOnPopoverChanged(() { + if (_focusNode.hasFocus) { + _focusNode.unfocus(); + } + }); + super.initState(); + } @override Widget build(BuildContext context) { @@ -258,6 +287,7 @@ class _CreateOptionTextField extends StatelessWidget { autoClearWhenDone: true, maxLength: 30, text: text, + focusNode: _focusNode, onCanceled: () { context .read() diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart index 44200ba5c2..45e1c3ac89 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/header/type_option/select_option_editor.dart @@ -1,7 +1,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/edit_select_option_bloc.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -62,10 +62,13 @@ class SelectOptionTypeOptionEditor extends StatelessWidget { return SizedBox( width: 160, - child: CustomScrollView( - slivers: slivers, - controller: ScrollController(), - physics: StyledScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(6.0), + child: CustomScrollView( + slivers: slivers, + controller: ScrollController(), + physics: StyledScrollPhysics(), + ), ), ); }, @@ -80,14 +83,15 @@ class _DeleteTag extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(LocaleKeys.grid_selectOption_deleteTag.tr(), fontSize: 12), - hoverColor: theme.hover, - leftIcon: svgWidget("grid/delete", color: theme.iconColor), + leftIcon: svgWidget( + "grid/delete", + color: Theme.of(context).colorScheme.onSurface, + ), onTap: () { context .read() @@ -141,8 +145,9 @@ class SelectOptionColorList extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyText.medium( LocaleKeys.grid_selectOption_colorPanelTitle.tr(), - fontSize: 12, + fontSize: FontSizes.s12, textAlign: TextAlign.left, + color: Theme.of(context).hintColor, ), ), ), @@ -172,7 +177,6 @@ class _SelectOptionColorCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); Widget? checkmark; if (isSelected) { checkmark = svgWidget("grid/checkmark"); @@ -192,7 +196,6 @@ class _SelectOptionColorCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(color.optionName(), fontSize: 12), - hoverColor: theme.hover, leftIcon: colorIcon, rightIcon: checkmark, onTap: () { diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart index 79643f48fb..7f489218e0 100755 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/grid_row.dart @@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/foundation.dart'; @@ -152,10 +151,8 @@ class _InsertButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return FlowyIconButton( tooltipText: LocaleKeys.tooltip_addNewRow.tr(), - hoverColor: theme.hover, width: 20, height: 30, onPressed: () => context.read().add(const RowEvent.createRow()), @@ -184,10 +181,8 @@ class _MenuButtonState extends State<_MenuButton> { @override Widget build(BuildContext context) { - final theme = context.watch(); return FlowyIconButton( tooltipText: LocaleKeys.tooltip_openMenu.tr(), - hoverColor: theme.hover, width: 20, height: 30, onPressed: () => widget.openMenu(), diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart index b944bc24ad..3182ae85a4 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_action_sheet.dart @@ -2,7 +2,6 @@ import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dar import 'package:easy_localization/easy_localization.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -56,23 +55,23 @@ class _RowActionCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return SizedBox( height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium( action.title(), fontSize: 12, - color: action.enable() ? theme.textColor : theme.shader3, + color: action.enable() ? null : Theme.of(context).disabledColor, ), - hoverColor: theme.hover, onTap: () { if (action.enable()) { action.performAction(context); } }, - leftIcon: svgWidget(action.iconName(), color: theme.iconColor), + leftIcon: svgWidget( + action.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart index 51a161be57..d8f7786ca1 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/row/row_detail.dart @@ -3,8 +3,8 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -59,7 +59,7 @@ class _RowDetailPageState extends State { child: Column( children: [ SizedBox( - height: 40, + height: 30, child: Row( children: const [Spacer(), _CloseButton()], ), @@ -83,14 +83,16 @@ class _CloseButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return FlowyIconButton( width: 24, onPressed: () { FlowyOverlay.pop(context); }, iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), - icon: svgWidget("home/close", color: theme.iconColor), + icon: svgWidget( + "home/close", + color: Theme.of(context).colorScheme.onSurface, + ), ); } } @@ -187,8 +189,6 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> { @override Widget build(BuildContext context) { - final theme = context.read(); - return AppFlowyPopover( constraints: BoxConstraints.loose(const Size(240, 200)), controller: popoverController, @@ -202,7 +202,7 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> { LocaleKeys.grid_field_newColumn.tr(), fontSize: 12, ), - hoverColor: theme.shader6, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onTap: () {}, leftIcon: svgWidget("home/add"), ), @@ -229,10 +229,10 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> { } BoxDecoration _makeBoxDecoration(BuildContext context) { - final theme = context.read(); - final borderSide = BorderSide(color: theme.shader6, width: 1.0); + final borderSide = + BorderSide(color: Theme.of(context).dividerColor, width: 1.0); return BoxDecoration( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, border: Border(top: borderSide), ); } @@ -256,23 +256,21 @@ class _RowDetailCellState extends State<_RowDetailCell> { @override Widget build(BuildContext context) { - final theme = context.watch(); - final style = _customCellStyle(theme, widget.cellId.fieldType); + final style = _customCellStyle(widget.cellId.fieldType); final cell = widget.cellBuilder.build(widget.cellId, style: style); final gesture = GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => cell.beginFocus.notify(), child: AccessoryHover( - contentPadding: - const EdgeInsets.symmetric(horizontal: 10, vertical: 12), + contentPadding: const EdgeInsets.symmetric(horizontal: 3, vertical: 3), child: cell, ), ); return IntrinsicHeight( child: ConstrainedBox( - constraints: const BoxConstraints(minHeight: 40), + constraints: const BoxConstraints(minHeight: 30), child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.center, @@ -287,9 +285,7 @@ class _RowDetailCellState extends State<_RowDetailCell> { child: FieldCellButton( maxLines: null, field: widget.cellId.fieldContext.field, - onTap: () { - popover.show(); - }, + onTap: () => popover.show(), ), ), ), @@ -326,7 +322,7 @@ class _RowDetailCellState extends State<_RowDetailCell> { } } -GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) { +GridCellStyle? _customCellStyle(FieldType fieldType) { switch (fieldType) { case FieldType.Checkbox: return null; diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart index dc3461b2e7..cba82dc8ad 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_group.dart @@ -2,7 +2,6 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -71,8 +70,6 @@ class _GridGroupCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.read(); - Widget? rightIcon; if (fieldContext.isGroupField) { rightIcon = Padding( @@ -85,10 +82,9 @@ class _GridGroupCell extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( text: FlowyText.medium(fieldContext.name, fontSize: 12), - hoverColor: theme.hover, leftIcon: svgWidget( fieldContext.fieldType.iconName(), - color: theme.iconColor, + color: Theme.of(context).colorScheme.onSurface, ), rightIcon: rightIcon, onTap: () { diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart index 8cc55c8265..1c3fa4a5a0 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_property.dart @@ -5,7 +5,6 @@ import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -87,22 +86,20 @@ class _GridPropertyCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - - final checkmark = fieldContext.visibility - ? svgWidget('home/show', color: theme.iconColor) - : svgWidget('home/hide', color: theme.iconColor); + final checkmark = svgWidget( + fieldContext.visibility ? 'home/show' : 'home/hide', + color: Theme.of(context).colorScheme.onSurface, + ); return Row( children: [ Expanded( child: SizedBox( height: GridSize.typeOptionItemHeight, - child: _editFieldButton(theme, context), + child: _editFieldButton(context), ), ), FlowyIconButton( - hoverColor: theme.hover, width: GridSize.typeOptionItemHeight, onPressed: () { context.read().add( @@ -115,16 +112,17 @@ class _GridPropertyCell extends StatelessWidget { ); } - Widget _editFieldButton(AppTheme theme, BuildContext context) { + Widget _editFieldButton(BuildContext context) { return AppFlowyPopover( mutex: popoverMutex, offset: const Offset(20, 0), constraints: BoxConstraints.loose(const Size(240, 400)), child: FlowyButton( text: FlowyText.medium(fieldContext.name, fontSize: 12), - hoverColor: theme.hover, - leftIcon: svgWidget(fieldContext.fieldType.iconName(), - color: theme.iconColor), + leftIcon: svgWidget( + fieldContext.fieldType.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), ), popupBuilder: (BuildContext context) { return FieldEditor( diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart index 961d20354f..14eee6f665 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_setting.dart @@ -1,7 +1,6 @@ import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -89,7 +88,6 @@ class _SettingItem extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); final isSelected = context .read() .state @@ -100,15 +98,20 @@ class _SettingItem extends StatelessWidget { height: GridSize.typeOptionItemHeight, child: FlowyButton( isSelected: isSelected, - text: FlowyText.medium(action.title(), - fontSize: 12, color: action.enable() ? null : theme.shader4), - hoverColor: theme.hover, + text: FlowyText.medium( + action.title(), + fontSize: 12, + color: action.enable() ? null : Theme.of(context).disabledColor, + ), onTap: () { context .read() .add(GridSettingEvent.performAction(action)); }, - leftIcon: svgWidget(action.iconName(), color: theme.iconColor), + leftIcon: svgWidget( + action.iconName(), + color: Theme.of(context).colorScheme.onSurface, + ), ), ); } diff --git a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart index 7b5ca129ce..12f5f0545a 100644 --- a/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart +++ b/frontend/app_flowy/lib/plugins/grid/presentation/widgets/toolbar/grid_toolbar.dart @@ -1,11 +1,9 @@ import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/extension.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../application/field/field_controller.dart'; import '../../layout/sizes.dart'; @@ -51,17 +49,15 @@ class _SettingButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return AppFlowyPopover( constraints: BoxConstraints.loose(const Size(260, 400)), offset: const Offset(0, 10), margin: const EdgeInsets.all(6), child: FlowyIconButton( width: 22, - hoverColor: theme.hover, icon: svgWidget( "grid/setting/setting", - color: theme.iconColor, + color: Theme.of(context).colorScheme.onSurface, ).padding(horizontal: 3, vertical: 3), ), popupBuilder: (BuildContext context) { diff --git a/frontend/app_flowy/lib/plugins/trash/application/trash_bloc.dart b/frontend/app_flowy/lib/plugins/trash/application/trash_bloc.dart index 0286747fd5..7e96fdc601 100644 --- a/frontend/app_flowy/lib/plugins/trash/application/trash_bloc.dart +++ b/frontend/app_flowy/lib/plugins/trash/application/trash_bloc.dart @@ -10,14 +10,16 @@ import 'package:app_flowy/plugins/trash/application/trash_listener.dart'; part 'trash_bloc.freezed.dart'; class TrashBloc extends Bloc { - final TrashService service; - final TrashListener listener; - TrashBloc({required this.service, required this.listener}) - : super(TrashState.init()) { + final TrashService _service; + final TrashListener _listener; + TrashBloc() + : _service = TrashService(), + _listener = TrashListener(), + super(TrashState.init()) { on((event, emit) async { await event.map(initial: (e) async { - listener.start(trashUpdated: _listenTrashUpdated); - final result = await service.readTrash(); + _listener.start(trashUpdated: _listenTrashUpdated); + final result = await _service.readTrash(); emit(result.fold( (object) => state.copyWith( objects: object.items, successOrFailure: left(unit)), @@ -26,17 +28,17 @@ class TrashBloc extends Bloc { }, didReceiveTrash: (e) async { emit(state.copyWith(objects: e.trash)); }, putback: (e) async { - final result = await service.putback(e.trashId); + final result = await _service.putback(e.trashId); await _handleResult(result, emit); }, delete: (e) async { final result = - await service.deleteViews([Tuple2(e.trash.id, e.trash.ty)]); + await _service.deleteViews([Tuple2(e.trash.id, e.trash.ty)]); await _handleResult(result, emit); }, deleteAll: (e) async { - final result = await service.deleteAll(); + final result = await _service.deleteAll(); await _handleResult(result, emit); }, restoreAll: (e) async { - final result = await service.restoreAll(); + final result = await _service.restoreAll(); await _handleResult(result, emit); }); }); @@ -63,7 +65,7 @@ class TrashBloc extends Bloc { @override Future close() async { - await listener.close(); + await _listener.close(); return super.close(); } } diff --git a/frontend/app_flowy/lib/plugins/trash/application/trash_listener.dart b/frontend/app_flowy/lib/plugins/trash/application/trash_listener.dart index 0b4e142058..843c4f1e82 100644 --- a/frontend/app_flowy/lib/plugins/trash/application/trash_listener.dart +++ b/frontend/app_flowy/lib/plugins/trash/application/trash_listener.dart @@ -8,7 +8,8 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart'; import 'package:flowy_sdk/rust_stream.dart'; -typedef TrashUpdatedCallback = void Function(Either, FlowyError> trashOrFailed); +typedef TrashUpdatedCallback = void Function( + Either, FlowyError> trashOrFailed); class TrashListener { StreamSubscription? _subscription; @@ -17,11 +18,13 @@ class TrashListener { void start({TrashUpdatedCallback? trashUpdated}) { _trashUpdated = trashUpdated; - _parser = FolderNotificationParser(callback: _bservableCallback); - _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable)); + _parser = FolderNotificationParser(callback: _observableCallback); + _subscription = + RustStreamReceiver.listen((observable) => _parser?.parse(observable)); } - void _bservableCallback(FolderNotification ty, Either result) { + void _observableCallback( + FolderNotification ty, Either result) { switch (ty) { case FolderNotification.TrashUpdated: if (_trashUpdated != null) { diff --git a/frontend/app_flowy/lib/plugins/trash/menu.dart b/frontend/app_flowy/lib/plugins/trash/menu.dart index 73fff65ba3..ec9e4e998f 100644 --- a/frontend/app_flowy/lib/plugins/trash/menu.dart +++ b/frontend/app_flowy/lib/plugins/trash/menu.dart @@ -1,6 +1,5 @@ import 'package:app_flowy/startup/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; -import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -8,9 +7,7 @@ import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:flowy_infra/theme.dart'; class MenuTrash extends StatelessWidget { const MenuTrash({Key? key}) : super(key: key); @@ -31,26 +28,19 @@ class MenuTrash extends StatelessWidget { } Widget _render(BuildContext context) { - return Row(children: [ - ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: Selector( - selector: (ctx, notifier) => notifier.theme, - builder: (ctx, theme, child) => SizedBox( - width: 16, - height: 16, - child: svgWidget("home/trash", color: theme.iconColor)), + return Row( + children: [ + SizedBox( + width: 16, + height: 16, + child: svgWidget( + "home/trash", + color: Theme.of(context).colorScheme.onSurface, + ), ), - ), - const HSpace(6), - ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: Selector( - selector: (ctx, notifier) => notifier.locale, - builder: (ctx, _, child) => - FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12), - ), - ), - ]); + const HSpace(6), + FlowyText.medium(LocaleKeys.trash_text.tr(), fontSize: 12), + ], + ); } } diff --git a/frontend/app_flowy/lib/plugins/trash/src/trash_cell.dart b/frontend/app_flowy/lib/plugins/trash/src/trash_cell.dart index 97f56ba89a..f7bf3bc7f4 100644 --- a/frontend/app_flowy/lib/plugins/trash/src/trash_cell.dart +++ b/frontend/app_flowy/lib/plugins/trash/src/trash_cell.dart @@ -1,5 +1,4 @@ import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -7,7 +6,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder/trash.pb.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:fixnum/fixnum.dart' as $fixnum; -import 'package:provider/provider.dart'; import 'sizes.dart'; @@ -24,7 +22,6 @@ class TrashCell extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Row( children: [ SizedBox( @@ -40,17 +37,21 @@ class TrashCell extends StatelessWidget { FlowyIconButton( width: 26, onPressed: onRestore, - hoverColor: theme.hover, iconPadding: const EdgeInsets.all(5), - icon: svgWidget("editor/restore", color: theme.iconColor), + icon: svgWidget( + "editor/restore", + color: Theme.of(context).colorScheme.onSurface, + ), ), const HSpace(20), FlowyIconButton( width: 26, onPressed: onDelete, - hoverColor: theme.hover, iconPadding: const EdgeInsets.all(5), - icon: svgWidget("editor/delete", color: theme.iconColor), + icon: svgWidget( + "editor/delete", + color: Theme.of(context).colorScheme.onSurface, + ), ), ], ); diff --git a/frontend/app_flowy/lib/plugins/trash/src/trash_header.dart b/frontend/app_flowy/lib/plugins/trash/src/trash_header.dart index dcabd05dda..4dc27770eb 100644 --- a/frontend/app_flowy/lib/plugins/trash/src/trash_header.dart +++ b/frontend/app_flowy/lib/plugins/trash/src/trash_header.dart @@ -1,8 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'sizes.dart'; @@ -11,7 +9,8 @@ class TrashHeaderDelegate extends SliverPersistentHeaderDelegate { TrashHeaderDelegate(); @override - Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { return TrashHeader(); } @@ -36,16 +35,21 @@ class TrashHeaderItem { class TrashHeader extends StatelessWidget { final List items = [ - TrashHeaderItem(title: LocaleKeys.trash_pageHeader_fileName.tr(), width: TrashSizes.fileNameWidth), - TrashHeaderItem(title: LocaleKeys.trash_pageHeader_lastModified.tr(), width: TrashSizes.lashModifyWidth), - TrashHeaderItem(title: LocaleKeys.trash_pageHeader_created.tr(), width: TrashSizes.createTimeWidth), + TrashHeaderItem( + title: LocaleKeys.trash_pageHeader_fileName.tr(), + width: TrashSizes.fileNameWidth), + TrashHeaderItem( + title: LocaleKeys.trash_pageHeader_lastModified.tr(), + width: TrashSizes.lashModifyWidth), + TrashHeaderItem( + title: LocaleKeys.trash_pageHeader_created.tr(), + width: TrashSizes.createTimeWidth), ]; TrashHeader({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - final theme = context.watch(); final headerItems = List.empty(growable: true); items.asMap().forEach((index, item) { headerItems.add( @@ -54,14 +58,14 @@ class TrashHeader extends StatelessWidget { child: FlowyText( item.title, fontSize: 12, - color: theme.shader3, + color: Theme.of(context).disabledColor, ), ), ); }); return Container( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ diff --git a/frontend/app_flowy/lib/plugins/trash/trash.dart b/frontend/app_flowy/lib/plugins/trash/trash.dart index 1661578267..0cc1e79e44 100644 --- a/frontend/app_flowy/lib/plugins/trash/trash.dart +++ b/frontend/app_flowy/lib/plugins/trash/trash.dart @@ -20,6 +20,9 @@ class TrashPluginBuilder extends PluginBuilder { @override String get menuName => "TrashPB"; + @override + String get menuIcon => "editor/delete"; + @override PluginType get pluginType => PluginType.trash; } diff --git a/frontend/app_flowy/lib/plugins/trash/trash_page.dart b/frontend/app_flowy/lib/plugins/trash/trash_page.dart index 2c0ed36633..7b8a07ce46 100644 --- a/frontend/app_flowy/lib/plugins/trash/trash_page.dart +++ b/frontend/app_flowy/lib/plugins/trash/trash_page.dart @@ -4,7 +4,6 @@ import 'package:app_flowy/plugins/trash/src/trash_header.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; @@ -29,7 +28,6 @@ class _TrashPageState extends State { final ScrollController _scrollController = ScrollController(); @override Widget build(BuildContext context) { - final theme = context.watch(); const horizontalPadding = 80.0; return BlocProvider( create: (context) => getIt()..add(const TrashEvent.initial()), @@ -39,7 +37,7 @@ class _TrashPageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ - _renderTopBar(context, theme, state), + _renderTopBar(context, state), const VSpace(32), _renderTrashList(context, state), ], @@ -82,7 +80,7 @@ class _TrashPageState extends State { ); } - Widget _renderTopBar(BuildContext context, AppTheme theme, TrashState state) { + Widget _renderTopBar(BuildContext context, TrashState state) { return SizedBox( height: 36, child: Row( @@ -94,8 +92,10 @@ class _TrashPageState extends State { child: FlowyButton( text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(), fontSize: 12), - leftIcon: svgWidget('editor/restore', color: theme.iconColor), - hoverColor: theme.hover, + leftIcon: svgWidget( + 'editor/restore', + color: Theme.of(context).colorScheme.onSurface, + ), onTap: () => context.read().add( const TrashEvent.restoreAll(), ), @@ -106,8 +106,10 @@ class _TrashPageState extends State { child: FlowyButton( text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(), fontSize: 12), - leftIcon: svgWidget('editor/delete', color: theme.iconColor), - hoverColor: theme.hover, + leftIcon: svgWidget( + 'editor/delete', + color: Theme.of(context).colorScheme.onSurface, + ), onTap: () => context.read().add(const TrashEvent.deleteAll()), ), diff --git a/frontend/app_flowy/lib/startup/deps_resolver.dart b/frontend/app_flowy/lib/startup/deps_resolver.dart index bd33115cde..9ea40681e0 100644 --- a/frontend/app_flowy/lib/startup/deps_resolver.dart +++ b/frontend/app_flowy/lib/startup/deps_resolver.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/core/network_monitor.dart'; import 'package:app_flowy/user/application/user_listener.dart'; import 'package:app_flowy/user/application/user_service.dart'; import 'package:app_flowy/workspace/application/app/prelude.dart'; -import 'package:app_flowy/plugins/doc/application/prelude.dart'; +import 'package:app_flowy/plugins/document/application/prelude.dart'; import 'package:app_flowy/plugins/grid/application/prelude.dart'; import 'package:app_flowy/workspace/application/user/prelude.dart'; import 'package:app_flowy/workspace/application/workspace/prelude.dart'; @@ -89,16 +89,6 @@ void _resolveFolderDeps(GetIt getIt) { getIt.registerFactoryParam( (view, _) => ViewBloc( view: view, - service: ViewService(), - listener: getIt(param1: view), - ), - ); - - //Menu - getIt.registerFactoryParam( - (user, workspaceId) => MenuBloc( - workspaceId: workspaceId, - listener: getIt(param1: user, param2: workspaceId), ), ); @@ -125,22 +115,14 @@ void _resolveFolderDeps(GetIt getIt) { getIt.registerLazySingleton(() => TrashService()); getIt.registerLazySingleton(() => TrashListener()); getIt.registerFactory( - () => TrashBloc( - service: getIt(), - listener: getIt(), - ), + () => TrashBloc(), ); } void _resolveDocDeps(GetIt getIt) { // Doc getIt.registerFactoryParam( - (view, _) => DocumentBloc( - view: view, - service: DocumentService(), - listener: getIt(param1: view), - trashService: getIt(), - ), + (view, _) => DocumentBloc(view: view), ); } @@ -158,10 +140,7 @@ void _resolveGridDeps(GetIt getIt) { ); getIt.registerFactoryParam( - (data, _) => FieldActionSheetBloc( - field: data.field, - fieldService: FieldService(gridId: data.gridId, fieldId: data.field.id), - ), + (data, _) => FieldActionSheetBloc(fieldCellContext: data), ); getIt.registerFactoryParam( diff --git a/frontend/app_flowy/lib/startup/plugin/plugin.dart b/frontend/app_flowy/lib/startup/plugin/plugin.dart index 8e7f88768f..bbde920789 100644 --- a/frontend/app_flowy/lib/startup/plugin/plugin.dart +++ b/frontend/app_flowy/lib/startup/plugin/plugin.dart @@ -47,9 +47,11 @@ abstract class PluginBuilder { String get menuName; + String get menuIcon; + PluginType get pluginType; - ViewDataTypePB get dataType => ViewDataTypePB.Text; + ViewDataFormatPB get dataFormatType => ViewDataFormatPB.TreeFormat; ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Document; } diff --git a/frontend/app_flowy/lib/startup/startup.dart b/frontend/app_flowy/lib/startup/startup.dart index f71df96f23..77d0cc3d33 100644 --- a/frontend/app_flowy/lib/startup/startup.dart +++ b/frontend/app_flowy/lib/startup/startup.dart @@ -39,9 +39,9 @@ class FlowyRunner { // add task getIt().addTask(InitRustSDKTask()); + getIt().addTask(PluginLoadTask()); if (!env.isTest()) { - getIt().addTask(PluginLoadTask()); getIt().addTask(InitAppWidgetTask()); getIt().addTask(InitPlatformServiceTask()); } diff --git a/frontend/app_flowy/lib/startup/tasks/app_widget.dart b/frontend/app_flowy/lib/startup/tasks/app_widget.dart index 807e308d7a..8ec181d9e3 100644 --- a/frontend/app_flowy/lib/startup/tasks/app_widget.dart +++ b/frontend/app_flowy/lib/startup/tasks/app_widget.dart @@ -1,14 +1,14 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/user/application/user_settings_service.dart'; import 'package:app_flowy/workspace/application/appearance.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -import 'package:window_size/window_size.dart'; -import 'package:bloc/bloc.dart'; import 'package:flowy_sdk/log.dart'; +import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:window_size/window_size.dart'; class InitAppWidgetTask extends LaunchTask { @override @@ -17,10 +17,9 @@ class InitAppWidgetTask extends LaunchTask { @override Future initialize(LaunchContext context) async { final widget = context.getIt().create(); - final setting = await SettingsFFIService().getAppearanceSetting(); - final settingModel = AppearanceSetting(setting); + final appearanceSetting = await SettingsFFIService().getAppearanceSetting(); final app = ApplicationWidget( - settingModel: settingModel, + appearanceSetting: appearanceSetting, child: widget, ); Bloc.observer = ApplicationBlocObserver(); @@ -42,6 +41,7 @@ class InitAppWidgetTask extends LaunchTask { Locale('pl', 'PL'), Locale('pt', 'BR'), Locale('ru', 'RU'), + Locale('sv'), Locale('tr', 'TR'), Locale('zh', 'CN'), ], @@ -59,49 +59,38 @@ class InitAppWidgetTask extends LaunchTask { class ApplicationWidget extends StatelessWidget { final Widget child; - final AppearanceSetting settingModel; + final AppearanceSettingsPB appearanceSetting; const ApplicationWidget({ Key? key, required this.child, - required this.settingModel, + required this.appearanceSetting, }) : super(key: key); @override Widget build(BuildContext context) { - return ChangeNotifierProvider.value( - value: settingModel, - builder: (context, _) { - const ratio = 1.73; - const minWidth = 600.0; - setWindowMinSize(const Size(minWidth, minWidth / ratio)); - settingModel.readLocaleWhenAppLaunch(context); - AppTheme theme = context.select( - (value) => value.theme, - ); - Locale locale = context.select( - (value) => value.locale, - ); + const ratio = 1.73; + const minWidth = 600.0; + setWindowMinSize(const Size(minWidth, minWidth / ratio)); - return MultiProvider( - providers: [ - Provider.value(value: theme), - Provider.value(value: locale), - ], - builder: (context, _) { - return MaterialApp( - builder: overlayManagerBuilder(), - debugShowCheckedModeBanner: false, - theme: theme.themeData, - localizationsDelegates: context.localizationDelegates, - supportedLocales: context.supportedLocales, - locale: locale, - navigatorKey: AppGlobals.rootNavKey, - home: child, - ); - }, - ); - }, + final cubit = AppearanceSettingsCubit(appearanceSetting) + ..readLocaleWhenAppLaunch(context); + + return BlocProvider( + create: (context) => cubit, + child: BlocBuilder( + builder: (context, state) => MaterialApp( + builder: overlayManagerBuilder(), + debugShowCheckedModeBanner: false, + theme: state.theme.themeData, + localizationsDelegates: context.localizationDelegates + + [AppFlowyEditorLocalizations.delegate], + supportedLocales: context.supportedLocales, + locale: state.locale, + navigatorKey: AppGlobals.rootNavKey, + home: child, + ), + ), ); } } diff --git a/frontend/app_flowy/lib/startup/tasks/load_plugin.dart b/frontend/app_flowy/lib/startup/tasks/load_plugin.dart index 5eabc18064..5ee25b2286 100644 --- a/frontend/app_flowy/lib/startup/tasks/load_plugin.dart +++ b/frontend/app_flowy/lib/startup/tasks/load_plugin.dart @@ -2,7 +2,7 @@ import 'package:app_flowy/startup/plugin/plugin.dart'; import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/blank/blank.dart'; import 'package:app_flowy/plugins/board/board.dart'; -import 'package:app_flowy/plugins/doc/document.dart'; +import 'package:app_flowy/plugins/document/document.dart'; import 'package:app_flowy/plugins/grid/grid.dart'; import 'package:app_flowy/plugins/trash/trash.dart'; diff --git a/frontend/app_flowy/lib/user/application/user_listener.dart b/frontend/app_flowy/lib/user/application/user_listener.dart index 5483926e71..1fe0566400 100644 --- a/frontend/app_flowy/lib/user/application/user_listener.dart +++ b/frontend/app_flowy/lib/user/application/user_listener.dart @@ -10,7 +10,8 @@ import 'package:flowy_infra/notifier.dart'; import 'package:flowy_sdk/protobuf/dart-notify/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; -import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user; +import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' + as user; import 'package:flowy_sdk/rust_stream.dart'; typedef UserProfileNotifyValue = Either; @@ -39,7 +40,8 @@ class UserListener { _authNotifier?.addPublishListener(onAuthChanged); } - _userParser = UserNotificationParser(id: _userProfile.token, callback: _userNotificationCallback); + _userParser = UserNotificationParser( + id: _userProfile.token, callback: _userNotificationCallback); _subscription = RustStreamReceiver.listen((observable) { _userParser?.parse(observable); }); @@ -55,7 +57,8 @@ class UserListener { _authNotifier = null; } - void _userNotificationCallback(user.UserNotification ty, Either result) { + void _userNotificationCallback( + user.UserNotification ty, Either result) { switch (ty) { case user.UserNotification.UserUnauthorized: result.fold( @@ -65,7 +68,8 @@ class UserListener { break; case user.UserNotification.UserProfileUpdated: result.fold( - (payload) => _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)), + (payload) => + _profileNotifier?.value = left(UserProfilePB.fromBuffer(payload)), (error) => _profileNotifier?.value = right(error), ); break; @@ -76,12 +80,14 @@ class UserListener { } typedef WorkspaceListNotifyValue = Either, FlowyError>; -typedef WorkspaceSettingNotifyValue = Either; +typedef WorkspaceSettingNotifyValue = Either; class UserWorkspaceListener { PublishNotifier? _authNotifier = PublishNotifier(); - PublishNotifier? _workspacesChangedNotifier = PublishNotifier(); - PublishNotifier? _settingChangedNotifier = PublishNotifier(); + PublishNotifier? _workspacesChangedNotifier = + PublishNotifier(); + PublishNotifier? _settingChangedNotifier = + PublishNotifier(); FolderNotificationListener? _listener; final UserProfilePB _userProfile; @@ -113,26 +119,30 @@ class UserWorkspaceListener { ); } - void _handleObservableType(FolderNotification ty, Either result) { + void _handleObservableType( + FolderNotification ty, Either result) { switch (ty) { case FolderNotification.UserCreateWorkspace: case FolderNotification.UserDeleteWorkspace: case FolderNotification.WorkspaceListUpdated: result.fold( - (payload) => _workspacesChangedNotifier?.value = left(RepeatedWorkspacePB.fromBuffer(payload).items), + (payload) => _workspacesChangedNotifier?.value = + left(RepeatedWorkspacePB.fromBuffer(payload).items), (error) => _workspacesChangedNotifier?.value = right(error), ); break; case FolderNotification.WorkspaceSetting: result.fold( - (payload) => _settingChangedNotifier?.value = left(CurrentWorkspaceSettingPB.fromBuffer(payload)), + (payload) => _settingChangedNotifier?.value = + left(WorkspaceSettingPB.fromBuffer(payload)), (error) => _settingChangedNotifier?.value = right(error), ); break; case FolderNotification.UserUnauthorized: result.fold( (_) {}, - (error) => _authNotifier?.value = right(FlowyError.create()..code = ErrorCode.UserUnauthorized.value), + (error) => _authNotifier?.value = right( + FlowyError.create()..code = ErrorCode.UserUnauthorized.value), ); break; default: diff --git a/frontend/app_flowy/lib/user/application/user_settings_service.dart b/frontend/app_flowy/lib/user/application/user_settings_service.dart index b420af505c..ff5fe46270 100644 --- a/frontend/app_flowy/lib/user/application/user_settings_service.dart +++ b/frontend/app_flowy/lib/user/application/user_settings_service.dart @@ -2,6 +2,7 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/flowy_sdk.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart'; class SettingsFFIService { @@ -18,6 +19,10 @@ class SettingsFFIService { ); } + Future> getUserSetting() { + return UserEventGetUserSetting().send(); + } + Future> setAppearanceSetting( AppearanceSettingsPB setting) { return UserEventSetAppearanceSetting(setting).send(); diff --git a/frontend/app_flowy/lib/user/presentation/router.dart b/frontend/app_flowy/lib/user/presentation/router.dart index 82ff46ada9..e073c18dd1 100644 --- a/frontend/app_flowy/lib/user/presentation/router.dart +++ b/frontend/app_flowy/lib/user/presentation/router.dart @@ -12,9 +12,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart'; import 'package:flutter/material.dart'; class AuthRouter { - void pushForgetPasswordScreen(BuildContext context) { - // TODO: implement showForgetPasswordScreen - } + void pushForgetPasswordScreen(BuildContext context) {} void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) { getIt().pushWelcomeScreen(context, userProfile); @@ -29,7 +27,7 @@ class AuthRouter { } void pushHomeScreen(BuildContext context, UserProfilePB profile, - CurrentWorkspaceSettingPB workspaceSetting) { + WorkspaceSettingPB workspaceSetting) { Navigator.push( context, PageRoutes.fade(() => HomeScreen(profile, workspaceSetting), @@ -54,7 +52,7 @@ class SplashRoute { } void pushHomeScreen(BuildContext context, UserProfilePB userProfile, - CurrentWorkspaceSettingPB workspaceSetting) { + WorkspaceSettingPB workspaceSetting) { Navigator.push( context, PageRoutes.fade(() => HomeScreen(userProfile, workspaceSetting), diff --git a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart index 6f17404474..6d78da638c 100644 --- a/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/sign_in_screen.dart @@ -4,7 +4,8 @@ import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/widgets/background.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -16,6 +17,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_infra/image.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class SignInScreen extends StatelessWidget { final AuthRouter router; @@ -39,7 +41,8 @@ class SignInScreen extends StatelessWidget { ); } - void _handleSuccessOrFail(Either result, BuildContext context) { + void _handleSuccessOrFail( + Either result, BuildContext context) { result.fold( (user) => router.pushWelcomeScreen(context, user), (error) => showSnapBar(context, error.msg), @@ -92,19 +95,20 @@ class SignUpPrompt extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(LocaleKeys.signIn_dontHaveAnAccount.tr(), style: TextStyle(color: theme.shader3, fontSize: 12)), + FlowyText.medium( + LocaleKeys.signIn_dontHaveAnAccount.tr(), + fontSize: FontSizes.s12, + color: Theme.of(context).hintColor, + ), TextButton( - style: TextButton.styleFrom( - textStyle: const TextStyle(fontSize: 12), - ), + style: TextButton.styleFrom(textStyle: TextStyles.body1), onPressed: () => router.pushSignUpScreen(context), child: Text( LocaleKeys.signUp_buttonText.tr(), - style: TextStyle(color: theme.main1), + style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ), ], @@ -119,15 +123,13 @@ class LoginButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return RoundedTextButton( title: LocaleKeys.signIn_loginButtonText.tr(), height: 48, borderRadius: Corners.s10Border, - color: theme.main1, - onPressed: () { - context.read().add(const SignInEvent.signedInWithUserEmailAndPassword()); - }, + onPressed: () => context + .read() + .add(const SignInEvent.signedInWithUserEmailAndPassword()), ); } } @@ -142,15 +144,14 @@ class ForgetPasswordButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return TextButton( style: TextButton.styleFrom( - textStyle: const TextStyle(fontSize: 12), + textStyle: TextStyles.body1, ), onPressed: () => router.pushForgetPasswordScreen(context), child: Text( LocaleKeys.signIn_forgotPassword.tr(), - style: TextStyle(color: theme.main1), + style: TextStyle(color: Theme.of(context).colorScheme.primary), ), ); } @@ -163,21 +164,24 @@ class PasswordTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( - buildWhen: (previous, current) => previous.passwordError != current.passwordError, + buildWhen: (previous, current) => + previous.passwordError != current.passwordError, builder: (context, state) { return RoundedInputField( obscureText: true, - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), obscureIcon: svgWidget("home/hide"), obscureHideIcon: svgWidget("home/show"), hintText: LocaleKeys.signIn_passwordHint.tr(), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - cursorColor: theme.main1, - errorText: context.read().state.passwordError.fold(() => "", (error) => error), - onChanged: (value) => context.read().add(SignInEvent.passwordChanged(value)), + errorText: context + .read() + .state + .passwordError + .fold(() => "", (error) => error), + onChanged: (value) => context + .read() + .add(SignInEvent.passwordChanged(value)), ); }, ); @@ -191,18 +195,20 @@ class EmailTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( - buildWhen: (previous, current) => previous.emailError != current.emailError, + buildWhen: (previous, current) => + previous.emailError != current.emailError, builder: (context, state) { return RoundedInputField( hintText: LocaleKeys.signIn_emailHint.tr(), - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - cursorColor: theme.main1, - errorText: context.read().state.emailError.fold(() => "", (error) => error), - onChanged: (value) => context.read().add(SignInEvent.emailChanged(value)), + style: TextStyles.body1.size(FontSizes.s14), + errorText: context + .read() + .state + .emailError + .fold(() => "", (error) => error), + onChanged: (value) => + context.read().add(SignInEvent.emailChanged(value)), ); }, ); diff --git a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart index 75834f3836..4d61342f79 100644 --- a/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/sign_up_screen.dart @@ -3,7 +3,8 @@ import 'package:app_flowy/user/application/sign_up_bloc.dart'; import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/widgets/background.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -15,6 +16,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:dartz/dartz.dart'; import 'package:flowy_infra/image.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class SignUpScreen extends StatelessWidget { final AuthRouter router; @@ -36,7 +38,8 @@ class SignUpScreen extends StatelessWidget { ); } - void _handleSuccessOrFail(BuildContext context, Either result) { + void _handleSuccessOrFail( + BuildContext context, Either result) { result.fold( (user) => router.pushWelcomeScreen(context, user), (error) => showSnapBar(context, error.msg), @@ -84,18 +87,18 @@ class SignUpPrompt extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( LocaleKeys.signUp_alreadyHaveAnAccount.tr(), - style: TextStyle(color: theme.shader3, fontSize: 12), + style: TextStyle(color: Theme.of(context).hintColor, fontSize: 12), ), TextButton( - style: TextButton.styleFrom(textStyle: const TextStyle(fontSize: 12)), + style: TextButton.styleFrom(textStyle: TextStyles.body1), onPressed: () => Navigator.pop(context), - child: Text(LocaleKeys.signIn_buttonText.tr(), style: TextStyle(color: theme.main1)), + child: Text(LocaleKeys.signIn_buttonText.tr(), + style: TextStyle(color: Theme.of(context).colorScheme.primary)), ), ], ); @@ -109,13 +112,14 @@ class SignUpButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return RoundedTextButton( title: LocaleKeys.signUp_getStartedText.tr(), height: 48, - color: theme.main1, + color: Theme.of(context).colorScheme.primary, onPressed: () { - context.read().add(const SignUpEvent.signUpWithUserEmailAndPassword()); + context + .read() + .add(const SignUpEvent.signUpWithUserEmailAndPassword()); }, ); } @@ -128,21 +132,27 @@ class PasswordTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( - buildWhen: (previous, current) => previous.passwordError != current.passwordError, + buildWhen: (previous, current) => + previous.passwordError != current.passwordError, builder: (context, state) { return RoundedInputField( obscureText: true, obscureIcon: svgWidget("home/hide"), obscureHideIcon: svgWidget("home/show"), - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), hintText: LocaleKeys.signUp_passwordHint.tr(), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - cursorColor: theme.main1, - errorText: context.read().state.passwordError.fold(() => "", (error) => error), - onChanged: (value) => context.read().add(SignUpEvent.passwordChanged(value)), + normalBorderColor: Theme.of(context).colorScheme.outline, + errorBorderColor: Theme.of(context).colorScheme.error, + cursorColor: Theme.of(context).colorScheme.primary, + errorText: context + .read() + .state + .passwordError + .fold(() => "", (error) => error), + onChanged: (value) => context + .read() + .add(SignUpEvent.passwordChanged(value)), ); }, ); @@ -156,21 +166,27 @@ class RepeatPasswordTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( - buildWhen: (previous, current) => previous.repeatPasswordError != current.repeatPasswordError, + buildWhen: (previous, current) => + previous.repeatPasswordError != current.repeatPasswordError, builder: (context, state) { return RoundedInputField( obscureText: true, obscureIcon: svgWidget("home/hide"), obscureHideIcon: svgWidget("home/show"), - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + style: TextStyles.body1.size(FontSizes.s14), hintText: LocaleKeys.signUp_repeatPasswordHint.tr(), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - cursorColor: theme.main1, - errorText: context.read().state.repeatPasswordError.fold(() => "", (error) => error), - onChanged: (value) => context.read().add(SignUpEvent.repeatPasswordChanged(value)), + normalBorderColor: Theme.of(context).colorScheme.outline, + errorBorderColor: Theme.of(context).colorScheme.error, + cursorColor: Theme.of(context).colorScheme.primary, + errorText: context + .read() + .state + .repeatPasswordError + .fold(() => "", (error) => error), + onChanged: (value) => context + .read() + .add(SignUpEvent.repeatPasswordChanged(value)), ); }, ); @@ -184,18 +200,23 @@ class EmailTextField extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( - buildWhen: (previous, current) => previous.emailError != current.emailError, + buildWhen: (previous, current) => + previous.emailError != current.emailError, builder: (context, state) { return RoundedInputField( hintText: LocaleKeys.signUp_emailHint.tr(), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - normalBorderColor: theme.shader4, - errorBorderColor: theme.red, - cursorColor: theme.main1, - errorText: context.read().state.emailError.fold(() => "", (error) => error), - onChanged: (value) => context.read().add(SignUpEvent.emailChanged(value)), + normalBorderColor: Theme.of(context).colorScheme.outline, + errorBorderColor: Theme.of(context).colorScheme.error, + cursorColor: Theme.of(context).colorScheme.primary, + errorText: context + .read() + .state + .emailError + .fold(() => "", (error) => error), + onChanged: (value) => + context.read().add(SignUpEvent.emailChanged(value)), ); }, ); diff --git a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart index 6e3ae5ea52..b3edce7839 100644 --- a/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/skip_log_in_screen.dart @@ -3,7 +3,7 @@ import 'package:app_flowy/user/presentation/router.dart'; import 'package:app_flowy/user/presentation/widgets/background.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -13,7 +13,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:dartz/dartz.dart' as dartz; import 'package:app_flowy/generated/locale_keys.g.dart'; @@ -63,7 +63,7 @@ class _SkipLogInScreenState extends State { InkWell( child: Text( LocaleKeys.githubStarText.tr(), - style: const TextStyle(decoration: TextDecoration.underline, color: Colors.blue), + style: TextStyles.general(color: Colors.blue).underline, ), onTap: () { _launchURL('https://github.com/AppFlowy-IO/appflowy'); @@ -72,7 +72,7 @@ class _SkipLogInScreenState extends State { InkWell( child: Text( LocaleKeys.subscribeNewsletterText.tr(), - style: const TextStyle(decoration: TextDecoration.underline, color: Colors.blue), + style: TextStyles.general(color: Colors.blue).underline, ), onTap: () { _launchURL('https://www.appflowy.io/blog'); @@ -104,7 +104,7 @@ class _SkipLogInScreenState extends State { ); result.fold( (user) { - FolderEventReadCurWorkspace().send().then((result) { + FolderEventReadCurrentWorkspace().send().then((result) { _openCurrentWorkspace(context, user, result); }); }, @@ -117,7 +117,7 @@ class _SkipLogInScreenState extends State { void _openCurrentWorkspace( BuildContext context, UserProfilePB user, - dartz.Either workspacesOrError, + dartz.Either workspacesOrError, ) { workspacesOrError.fold( (workspaceSetting) { @@ -139,12 +139,11 @@ class GoButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return RoundedTextButton( title: LocaleKeys.letsGoButtonText.tr(), height: 50, borderRadius: Corners.s10Border, - color: theme.main1, + color: Theme.of(context).colorScheme.primary, onPressed: onPressed, ); } diff --git a/frontend/app_flowy/lib/user/presentation/splash_screen.dart b/frontend/app_flowy/lib/user/presentation/splash_screen.dart index 9fd919e7eb..9f89e00ed1 100644 --- a/frontend/app_flowy/lib/user/presentation/splash_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/splash_screen.dart @@ -44,10 +44,11 @@ class SplashScreen extends StatelessWidget { void _handleAuthenticated(BuildContext context, Authenticated result) { final userProfile = result.userProfile; - FolderEventReadCurWorkspace().send().then( + FolderEventReadCurrentWorkspace().send().then( (result) { return result.fold( - (workspaceSetting) => getIt().pushHomeScreen(context, userProfile, workspaceSetting), + (workspaceSetting) => getIt() + .pushHomeScreen(context, userProfile, workspaceSetting), (error) async { Log.error(error); assert(error.code == ErrorCode.RecordNotFound.value); @@ -80,7 +81,8 @@ class Body extends StatelessWidget { fit: BoxFit.cover, width: size.width, height: size.height, - image: const AssetImage('assets/images/appflowy_launch_splash.jpg')), + image: const AssetImage( + 'assets/images/appflowy_launch_splash.jpg')), const CircularProgressIndicator.adaptive(), ], ), diff --git a/frontend/app_flowy/lib/user/presentation/welcome_screen.dart b/frontend/app_flowy/lib/user/presentation/welcome_screen.dart index 31b06d8bd1..3525b5c252 100644 --- a/frontend/app_flowy/lib/user/presentation/welcome_screen.dart +++ b/frontend/app_flowy/lib/user/presentation/welcome_screen.dart @@ -1,7 +1,7 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; @@ -21,7 +21,8 @@ class WelcomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => getIt(param1: userProfile)..add(const WelcomeEvent.initial()), + create: (_) => getIt(param1: userProfile) + ..add(const WelcomeEvent.initial()), child: BlocBuilder( builder: (context, state) { return Scaffold( @@ -49,17 +50,16 @@ class WelcomeScreen extends StatelessWidget { } Widget _renderCreateButton(BuildContext context) { - final theme = context.watch(); - return SizedBox( width: 200, height: 40, child: FlowyTextButton( LocaleKeys.workspace_create.tr(), fontSize: 14, - hoverColor: theme.bg3, + hoverColor: AFThemeExtension.of(context).lightGreyHover, onPressed: () { - context.read().add(WelcomeEvent.createWorkspace(LocaleKeys.workspace_hint.tr(), "")); + context.read().add( + WelcomeEvent.createWorkspace(LocaleKeys.workspace_hint.tr(), "")); }, ), ); @@ -90,17 +90,17 @@ class WelcomeScreen extends StatelessWidget { class WorkspaceItem extends StatelessWidget { final WorkspacePB workspace; final void Function(WorkspacePB workspace) onPressed; - const WorkspaceItem({Key? key, required this.workspace, required this.onPressed}) : super(key: key); + const WorkspaceItem( + {Key? key, required this.workspace, required this.onPressed}) + : super(key: key); @override Widget build(BuildContext context) { - final theme = context.watch(); - return SizedBox( height: 46, child: FlowyTextButton( workspace.name, - hoverColor: theme.bg3, + hoverColor: AFThemeExtension.of(context).lightGreyHover, fontSize: 14, onPressed: () => onPressed(workspace), ), diff --git a/frontend/app_flowy/lib/user/presentation/widgets/background.dart b/frontend/app_flowy/lib/user/presentation/widgets/background.dart index 225f7f0080..c7d7b25e3c 100644 --- a/frontend/app_flowy/lib/user/presentation/widgets/background.dart +++ b/frontend/app_flowy/lib/user/presentation/widgets/background.dart @@ -1,10 +1,9 @@ import 'dart:math'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; class AuthFormContainer extends StatelessWidget { final List children; @@ -37,7 +36,6 @@ class FlowyLogoTitle extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SizedBox( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -47,14 +45,10 @@ class FlowyLogoTitle extends StatelessWidget { child: svgWidget("flowy_logo"), ), const VSpace(30), - Text( + FlowyText.semibold( title, - style: TextStyle( - color: theme.textColor, - fontWeight: FontWeight.w600, - fontSize: 24, - ), - ) + fontSize: 24, + ), ], ), ); diff --git a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart index fa2a9555bc..187a7155ad 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_bloc.dart @@ -18,11 +18,10 @@ import 'package:dartz/dartz.dart'; part 'app_bloc.freezed.dart'; class AppBloc extends Bloc { - final AppPB app; final AppService appService; final AppListener appListener; - AppBloc({required this.app}) + AppBloc({required AppPB app}) : appService = AppService(), appListener = AppListener(appId: app.id), super(AppState.initial(app)) { @@ -34,8 +33,6 @@ class AppBloc extends Bloc { await _createView(value, emit); }, loadViews: (_) async { await _loadViews(emit); - }, didReceiveViewUpdated: (e) async { - await _didReceiveViewUpdated(e.views, emit); }, delete: (e) async { await _deleteApp(emit); }, deleteView: (deletedView) async { @@ -43,23 +40,26 @@ class AppBloc extends Bloc { }, rename: (e) async { await _renameView(e, emit); }, appDidUpdate: (e) async { - emit(state.copyWith(app: e.app)); + final latestCreatedView = state.latestCreatedView; + final views = e.app.belongings.items; + AppState newState = state.copyWith( + views: views, + app: e.app, + ); + if (latestCreatedView != null) { + final index = + views.indexWhere((element) => element.id == latestCreatedView.id); + if (index == -1) { + newState = newState.copyWith(latestCreatedView: null); + } + } + emit(newState); }); }); } void _startListening() { appListener.start( - onViewsChanged: (result) { - result.fold( - (views) { - if (!isClosed) { - add(AppEvent.didReceiveViewUpdated(views)); - } - }, - (error) => Log.error(error), - ); - }, onAppUpdated: (app) { if (!isClosed) { add(AppEvent.appDidUpdate(app)); @@ -69,7 +69,8 @@ class AppBloc extends Bloc { } Future _renameView(Rename e, Emitter emit) async { - final result = await appService.updateApp(appId: app.id, name: e.newName); + final result = + await appService.updateApp(appId: state.app.id, name: e.newName); result.fold( (l) => emit(state.copyWith(successOrFailure: left(unit))), (error) => emit(state.copyWith(successOrFailure: right(error))), @@ -78,7 +79,7 @@ class AppBloc extends Bloc { // Delete the current app Future _deleteApp(Emitter emit) async { - final result = await appService.delete(appId: app.id); + final result = await appService.delete(appId: state.app.id); result.fold( (unit) => emit(state.copyWith(successOrFailure: left(unit))), (error) => emit(state.copyWith(successOrFailure: right(error))), @@ -95,10 +96,10 @@ class AppBloc extends Bloc { Future _createView(CreateView value, Emitter emit) async { final result = await appService.createView( - appId: app.id, + appId: state.app.id, name: value.name, desc: value.desc ?? "", - dataType: value.pluginBuilder.dataType, + dataFormatType: value.pluginBuilder.dataFormatType, pluginType: value.pluginBuilder.pluginType, layoutType: value.pluginBuilder.layoutType!, ); @@ -120,25 +121,8 @@ class AppBloc extends Bloc { return super.close(); } - Future _didReceiveViewUpdated( - List views, - Emitter emit, - ) async { - final latestCreatedView = state.latestCreatedView; - AppState newState = state.copyWith(views: views); - if (latestCreatedView != null) { - final index = - views.indexWhere((element) => element.id == latestCreatedView.id); - if (index == -1) { - newState = newState.copyWith(latestCreatedView: null); - } - } - - emit(newState); - } - Future _loadViews(Emitter emit) async { - final viewsOrFailed = await appService.getViews(appId: app.id); + final viewsOrFailed = await appService.getViews(appId: state.app.id); viewsOrFailed.fold( (views) => emit(state.copyWith(views: views)), (error) { @@ -161,8 +145,6 @@ class AppEvent with _$AppEvent { const factory AppEvent.delete() = DeleteApp; const factory AppEvent.deleteView(String viewId) = DeleteView; const factory AppEvent.rename(String newName) = Rename; - const factory AppEvent.didReceiveViewUpdated(List views) = - ReceiveViews; const factory AppEvent.appDidUpdate(AppPB app) = AppDidUpdate; } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_listener.dart b/frontend/app_flowy/lib/workspace/application/app/app_listener.dart index 6edf1a4df2..7019200261 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_listener.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_listener.dart @@ -11,11 +11,11 @@ import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart'; import 'package:flowy_sdk/rust_stream.dart'; typedef AppDidUpdateCallback = void Function(AppPB app); -typedef ViewsDidChangeCallback = void Function(Either, FlowyError> viewsOrFailed); +typedef ViewsDidChangeCallback = void Function( + Either, FlowyError> viewsOrFailed); class AppListener { StreamSubscription? _subscription; - ViewsDidChangeCallback? _viewsChanged; AppDidUpdateCallback? _updated; FolderNotificationParser? _parser; String appId; @@ -24,26 +24,16 @@ class AppListener { required this.appId, }); - void start({ViewsDidChangeCallback? onViewsChanged, AppDidUpdateCallback? onAppUpdated}) { - _viewsChanged = onViewsChanged; + void start({AppDidUpdateCallback? onAppUpdated}) { _updated = onAppUpdated; - _parser = FolderNotificationParser(id: appId, callback: _bservableCallback); - _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable)); + _parser = FolderNotificationParser(id: appId, callback: _handleCallback); + _subscription = + RustStreamReceiver.listen((observable) => _parser?.parse(observable)); } - void _bservableCallback(FolderNotification ty, Either result) { + void _handleCallback( + FolderNotification ty, Either result) { switch (ty) { - case FolderNotification.AppViewsChanged: - if (_viewsChanged != null) { - result.fold( - (payload) { - final repeatedView = RepeatedViewPB.fromBuffer(payload); - _viewsChanged!(left(repeatedView.items)); - }, - (error) => _viewsChanged!(right(error)), - ); - } - break; case FolderNotification.AppUpdated: if (_updated != null) { result.fold( @@ -63,7 +53,6 @@ class AppListener { Future stop() async { _parser = null; await _subscription?.cancel(); - _viewsChanged = null; _updated = null; } } diff --git a/frontend/app_flowy/lib/workspace/application/app/app_service.dart b/frontend/app_flowy/lib/workspace/application/app/app_service.dart index ab35c3338d..fd096a4e0c 100644 --- a/frontend/app_flowy/lib/workspace/application/app/app_service.dart +++ b/frontend/app_flowy/lib/workspace/application/app/app_service.dart @@ -19,7 +19,7 @@ class AppService { required String appId, required String name, String? desc, - required ViewDataTypePB dataType, + required ViewDataFormatPB dataFormatType, required PluginType pluginType, required ViewLayoutTypePB layoutType, }) { @@ -27,7 +27,7 @@ class AppService { ..belongToId = appId ..name = name ..desc = desc ?? "" - ..dataType = dataType + ..dataFormat = dataFormatType ..layout = layoutType; return FolderEventCreateView(payload).send(); diff --git a/frontend/app_flowy/lib/workspace/application/appearance.dart b/frontend/app_flowy/lib/workspace/application/appearance.dart index 1d3a13c7af..016559b1bc 100644 --- a/frontend/app_flowy/lib/workspace/application/appearance.dart +++ b/frontend/app_flowy/lib/workspace/application/appearance.dart @@ -1,52 +1,50 @@ import 'dart:async'; import 'package:app_flowy/user/application/user_settings_service.dart'; -import 'package:equatable/equatable.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart'; import 'package:flutter/material.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; -/// [AppearanceSetting] is used to modify the appear setting of AppFlowy application. Including the [Locale], [AppTheme], etc. -class AppearanceSetting extends ChangeNotifier with EquatableMixin { +part 'appearance.freezed.dart'; + +/// [AppearanceSettingsCubit] is used to modify the appear setting of AppFlowy application. Includes the [Locale] and [AppTheme]. +class AppearanceSettingsCubit extends Cubit { final AppearanceSettingsPB _setting; - AppTheme _theme; - Locale _locale; - Timer? _debounceSaveOperation; - AppearanceSetting(AppearanceSettingsPB setting) + AppearanceSettingsCubit(AppearanceSettingsPB setting) : _setting = setting, - _theme = AppTheme.fromName(name: setting.theme), - _locale = Locale( - setting.locale.languageCode, - setting.locale.countryCode, - ); - - /// Returns the current [AppTheme] - AppTheme get theme => _theme; - - /// Returns the current [Locale] - Locale get locale => _locale; + super(AppearanceSettingsState.initial( + setting.theme, + setting.font, + setting.monospaceFont, + setting.locale, + )); /// Updates the current theme and notify the listeners the theme was changed. /// Do nothing if the passed in themeType equal to the current theme type. - /// - void setTheme(ThemeType themeType) { - if (_theme.ty == themeType) { + void setTheme(Brightness brightness) { + if (state.theme.brightness == brightness) { return; } - _theme = AppTheme.fromType(themeType); - _setting.theme = themeTypeToString(themeType); - _saveAppearSetting(); + _setting.theme = themeTypeToString(brightness); + _saveAppearanceSettings(); - notifyListeners(); + emit(state.copyWith( + theme: AppTheme.fromName( + themeName: _setting.theme, + font: state.theme.font, + monospaceFont: state.theme.monospaceFont, + ), + )); } /// Updates the current locale and notify the listeners the locale was changed /// Fallback to [en] locale If the newLocale is not supported. - /// void setLocale(BuildContext context, Locale newLocale) { if (!context.supportedLocales.contains(newLocale)) { Log.warn("Unsupported locale: $newLocale, Fallback to locale: en"); @@ -55,13 +53,12 @@ class AppearanceSetting extends ChangeNotifier with EquatableMixin { context.setLocale(newLocale); - if (_locale != newLocale) { - _locale = newLocale; - _setting.locale.languageCode = _locale.languageCode; - _setting.locale.countryCode = _locale.countryCode ?? ""; - _saveAppearSetting(); + if (state.locale != newLocale) { + _setting.locale.languageCode = newLocale.languageCode; + _setting.locale.countryCode = newLocale.countryCode ?? ""; + _saveAppearanceSettings(); - notifyListeners(); + emit(state.copyWith(locale: newLocale)); } } @@ -83,10 +80,16 @@ class AppearanceSetting extends ChangeNotifier with EquatableMixin { } else { _setting.settingKeyValue[key] = value; } - - _saveAppearSetting(); - notifyListeners(); } + _saveAppearanceSettings(); + } + + String? getValue(String key) { + if (key.isEmpty) { + Log.warn("The key should not be empty"); + return null; + } + return _setting.settingKeyValue[key]; } /// Called when the application launch. @@ -94,28 +97,43 @@ class AppearanceSetting extends ChangeNotifier with EquatableMixin { void readLocaleWhenAppLaunch(BuildContext context) { if (_setting.resetToDefault) { _setting.resetToDefault = false; - _saveAppearSetting(); + _saveAppearanceSettings(); setLocale(context, context.deviceLocale); return; } - setLocale(context, _locale); + setLocale(context, state.locale); } - Future _saveAppearSetting() async { - _debounceSaveOperation?.cancel(); - _debounceSaveOperation = Timer( - const Duration(seconds: 1), - () { - SettingsFFIService().setAppearanceSetting(_setting).then((result) { - result.fold((l) => null, (error) => Log.error(error)); - }); - }, - ); - } - - @override - List get props { - return [_setting.hashCode]; + Future _saveAppearanceSettings() async { + SettingsFFIService().setAppearanceSetting(_setting).then((result) { + result.fold( + (l) => null, + (error) => Log.error(error), + ); + }); } } + +@freezed +class AppearanceSettingsState with _$AppearanceSettingsState { + const factory AppearanceSettingsState({ + required AppTheme theme, + required Locale locale, + }) = _AppearanceSettingsState; + + factory AppearanceSettingsState.initial( + String themeName, + String font, + String monospaceFont, + LocaleSettingsPB locale, + ) => + AppearanceSettingsState( + theme: AppTheme.fromName( + themeName: themeName, + font: font, + monospaceFont: monospaceFont, + ), + locale: Locale(locale.languageCode, locale.countryCode), + ); +} diff --git a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart index d325fc7705..5c3455454a 100644 --- a/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/home/home_bloc.dart @@ -5,7 +5,7 @@ import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart' - show CurrentWorkspaceSettingPB; + show WorkspaceSettingPB; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -15,8 +15,10 @@ part 'home_bloc.freezed.dart'; class HomeBloc extends Bloc { final UserWorkspaceListener _listener; - HomeBloc(UserProfilePB user, CurrentWorkspaceSettingPB workspaceSetting) - : _listener = UserWorkspaceListener(userProfile: user), + HomeBloc( + UserProfilePB user, + WorkspaceSettingPB workspaceSetting, + ) : _listener = UserWorkspaceListener(userProfile: user), super(HomeState.initial(workspaceSetting)) { on( (event, emit) async { @@ -115,7 +117,7 @@ class HomeEvent with _$HomeEvent { _ShowEditPanel; const factory HomeEvent.dismissEditPanel() = _DismissEditPanel; const factory HomeEvent.didReceiveWorkspaceSetting( - CurrentWorkspaceSettingPB setting) = _DidReceiveWorkspaceSetting; + WorkspaceSettingPB setting) = _DidReceiveWorkspaceSetting; const factory HomeEvent.unauthorized(String msg) = _Unauthorized; const factory HomeEvent.collapseMenu() = _CollapseMenu; const factory HomeEvent.editPanelResized(double offset) = _EditPanelResized; @@ -129,7 +131,7 @@ class HomeState with _$HomeState { required bool isLoading, required bool forceCollapse, required Option panelContext, - required CurrentWorkspaceSettingPB workspaceSetting, + required WorkspaceSettingPB workspaceSetting, required bool unauthorized, required bool isMenuCollapsed, required double resizeOffset, @@ -137,8 +139,7 @@ class HomeState with _$HomeState { required MenuResizeType resizeType, }) = _HomeState; - factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) => - HomeState( + factory HomeState.initial(WorkspaceSettingPB workspaceSetting) => HomeState( isLoading: false, forceCollapse: false, panelContext: none(), diff --git a/frontend/app_flowy/lib/workspace/application/markdown/delta_markdown.dart b/frontend/app_flowy/lib/workspace/application/markdown/delta_markdown.dart deleted file mode 100644 index ef723f2697..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/delta_markdown.dart +++ /dev/null @@ -1,30 +0,0 @@ -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/frontend/app_flowy/lib/workspace/application/markdown/src/ast.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/ast.dart deleted file mode 100644 index 5356f1d05f..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/ast.dart +++ /dev/null @@ -1,113 +0,0 @@ -// 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/frontend/app_flowy/lib/workspace/application/markdown/src/block_parser.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/block_parser.dart deleted file mode 100644 index faac444b98..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/block_parser.dart +++ /dev/null @@ -1,1096 +0,0 @@ -// 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/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_decoder.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_decoder.dart deleted file mode 100644 index 74982c752e..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_decoder.dart +++ /dev/null @@ -1,255 +0,0 @@ -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 additional - // 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/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_encoder.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_encoder.dart deleted file mode 100644 index bd2aa4c281..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/delta_markdown_encoder.dart +++ /dev/null @@ -1,281 +0,0 @@ -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 if (attribute.key == Attribute.background.key) { - buffer.write(!close ? '' : ''); - } else if (attribute.key == Attribute.underline.key) { - buffer.write(!close ? '' : ''); - } else if (attribute.key == Attribute.codeBlock.key) { - buffer.write(!close ? '```\n' : '\n```'); - } else if (attribute.key == Attribute.inlineCode.key) { - buffer.write(!close ? '`' : '`'); - } else if (attribute.key == Attribute.strikeThrough.key) { - buffer.write(!close ? '~~' : '~~'); - } 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 if (block.key == Attribute.list.key) { - 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/frontend/app_flowy/lib/workspace/application/markdown/src/document.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/document.dart deleted file mode 100644 index 890b858cd2..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/document.dart +++ /dev/null @@ -1,88 +0,0 @@ -// 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/frontend/app_flowy/lib/workspace/application/markdown/src/emojis.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/emojis.dart deleted file mode 100644 index 3c6e893112..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/emojis.dart +++ /dev/null @@ -1,1818 +0,0 @@ -// This file was generated by 0xN0x using emojilib's emoji data file: -// https://github.com/muan/unicode-emoji-json/raw/main/data-by-emoji.json -// at 2021-12-01 10:10:53 -// Emoji version 13.1 - -const emojis = { - 'grinning_face': '😀', - 'grinning_face_with_big_eyes': '😃', - 'grinning_face_with_smiling_eyes': '😄', - 'beaming_face_with_smiling_eyes': '😁', - 'grinning_squinting_face': '😆', - 'grinning_face_with_sweat': '😅', - 'rolling_on_the_floor_laughing': '🤣', - 'face_with_tears_of_joy': '😂', - 'slightly_smiling_face': '🙂', - 'upside_down_face': '🙃', - 'winking_face': '😉', - 'smiling_face_with_smiling_eyes': '😊', - 'smiling_face_with_halo': '😇', - 'smiling_face_with_hearts': '🥰', - 'smiling_face_with_heart_eyes': '😍', - 'star_struck': '🤩', - 'face_blowing_a_kiss': '😘', - 'kissing_face': '😗', - 'smiling_face': '☺️', - 'kissing_face_with_closed_eyes': '😚', - 'kissing_face_with_smiling_eyes': '😙', - 'smiling_face_with_tear': '🥲', - 'face_savoring_food': '😋', - 'face_with_tongue': '😛', - 'winking_face_with_tongue': '😜', - 'zany_face': '🤪', - 'squinting_face_with_tongue': '😝', - 'money_mouth_face': '🤑', - 'hugging_face': '🤗', - 'face_with_hand_over_mouth': '🤭', - 'shushing_face': '🤫', - 'thinking_face': '🤔', - 'zipper_mouth_face': '🤐', - 'face_with_raised_eyebrow': '🤨', - 'neutral_face': '😐', - 'expressionless_face': '😑', - 'face_without_mouth': '😶', - 'face_in_clouds': '😶‍🌫️', - 'smirking_face': '😏', - 'unamused_face': '😒', - 'face_with_rolling_eyes': '🙄', - 'grimacing_face': '😬', - 'face_exhaling': '😮‍💨', - 'lying_face': '🤥', - 'relieved_face': '😌', - 'pensive_face': '😔', - 'sleepy_face': '😪', - 'drooling_face': '🤤', - 'sleeping_face': '😴', - 'face_with_medical_mask': '😷', - 'face_with_thermometer': '🤒', - 'face_with_head_bandage': '🤕', - 'nauseated_face': '🤢', - 'face_vomiting': '🤮', - 'sneezing_face': '🤧', - 'hot_face': '🥵', - 'cold_face': '🥶', - 'woozy_face': '🥴', - 'knocked_out_face': '😵', - 'face_with_spiral_eyes': '😵‍💫', - 'exploding_head': '🤯', - 'cowboy_hat_face': '🤠', - 'partying_face': '🥳', - 'disguised_face': '🥸', - 'smiling_face_with_sunglasses': '😎', - 'nerd_face': '🤓', - 'face_with_monocle': '🧐', - 'confused_face': '😕', - 'worried_face': '😟', - 'slightly_frowning_face': '🙁', - 'frowning_face': '☹️', - 'face_with_open_mouth': '😮', - 'hushed_face': '😯', - 'astonished_face': '😲', - 'flushed_face': '😳', - 'pleading_face': '🥺', - 'frowning_face_with_open_mouth': '😦', - 'anguished_face': '😧', - 'fearful_face': '😨', - 'anxious_face_with_sweat': '😰', - 'sad_but_relieved_face': '😥', - 'crying_face': '😢', - 'loudly_crying_face': '😭', - 'face_screaming_in_fear': '😱', - 'confounded_face': '😖', - 'persevering_face': '😣', - 'disappointed_face': '😞', - 'downcast_face_with_sweat': '😓', - 'weary_face': '😩', - 'tired_face': '😫', - 'yawning_face': '🥱', - 'face_with_steam_from_nose': '😤', - 'pouting_face': '😡', - 'angry_face': '😠', - 'face_with_symbols_on_mouth': '🤬', - 'smiling_face_with_horns': '😈', - 'angry_face_with_horns': '👿', - 'skull': '💀', - 'skull_and_crossbones': '☠️', - 'pile_of_poo': '💩', - 'clown_face': '🤡', - 'ogre': '👹', - 'goblin': '👺', - 'ghost': '👻', - 'alien': '👽', - 'alien_monster': '👾', - 'robot': '🤖', - 'grinning_cat': '😺', - 'grinning_cat_with_smiling_eyes': '😸', - 'cat_with_tears_of_joy': '😹', - 'smiling_cat_with_heart_eyes': '😻', - 'cat_with_wry_smile': '😼', - 'kissing_cat': '😽', - 'weary_cat': '🙀', - 'crying_cat': '😿', - 'pouting_cat': '😾', - 'see_no_evil_monkey': '🙈', - 'hear_no_evil_monkey': '🙉', - 'speak_no_evil_monkey': '🙊', - 'kiss_mark': '💋', - 'love_letter': '💌', - 'heart_with_arrow': '💘', - 'heart_with_ribbon': '💝', - 'sparkling_heart': '💖', - 'growing_heart': '💗', - 'beating_heart': '💓', - 'revolving_hearts': '💞', - 'two_hearts': '💕', - 'heart_decoration': '💟', - 'heart_exclamation': '❣️', - 'broken_heart': '💔', - 'heart_on_fire': '❤️‍🔥', - 'mending_heart': '❤️‍🩹', - 'red_heart': '❤️', - 'orange_heart': '🧡', - 'yellow_heart': '💛', - 'green_heart': '💚', - 'blue_heart': '💙', - 'purple_heart': '💜', - 'brown_heart': '🤎', - 'black_heart': '🖤', - 'white_heart': '🤍', - 'hundred_points': '💯', - 'anger_symbol': '💢', - 'collision': '💥', - 'dizzy': '💫', - 'sweat_droplets': '💦', - 'dashing_away': '💨', - 'hole': '🕳️', - 'bomb': '💣', - 'speech_balloon': '💬', - 'eye_in_speech_bubble': '👁️‍🗨️', - 'left_speech_bubble': '🗨️', - 'right_anger_bubble': '🗯️', - 'thought_balloon': '💭', - 'zzz': '💤', - 'waving_hand': '👋', - 'raised_back_of_hand': '🤚', - 'hand_with_fingers_splayed': '🖐️', - 'raised_hand': '✋', - 'vulcan_salute': '🖖', - 'ok_hand': '👌', - 'pinched_fingers': '🤌', - 'pinching_hand': '🤏', - 'victory_hand': '✌️', - 'crossed_fingers': '🤞', - 'love_you_gesture': '🤟', - 'sign_of_the_horns': '🤘', - 'call_me_hand': '🤙', - 'backhand_index_pointing_left': '👈', - 'backhand_index_pointing_right': '👉', - 'backhand_index_pointing_up': '👆', - 'middle_finger': '🖕', - 'backhand_index_pointing_down': '👇', - 'index_pointing_up': '☝️', - 'thumbs_up': '👍', - 'thumbs_down': '👎', - 'raised_fist': '✊', - 'oncoming_fist': '👊', - 'left_facing_fist': '🤛', - 'right_facing_fist': '🤜', - 'clapping_hands': '👏', - 'raising_hands': '🙌', - 'open_hands': '👐', - 'palms_up_together': '🤲', - 'handshake': '🤝', - 'folded_hands': '🙏', - 'writing_hand': '✍️', - 'nail_polish': '💅', - 'selfie': '🤳', - 'flexed_biceps': '💪', - 'mechanical_arm': '🦾', - 'mechanical_leg': '🦿', - 'leg': '🦵', - 'foot': '🦶', - 'ear': '👂', - 'ear_with_hearing_aid': '🦻', - 'nose': '👃', - 'brain': '🧠', - 'anatomical_heart': '🫀', - 'lungs': '🫁', - 'tooth': '🦷', - 'bone': '🦴', - 'eyes': '👀', - 'eye': '👁️', - 'tongue': '👅', - 'mouth': '👄', - 'baby': '👶', - 'child': '🧒', - 'boy': '👦', - 'girl': '👧', - 'person': '🧑', - 'person_blond_hair': '👱', - 'man': '👨', - 'person_beard': '🧔', - 'man_beard': '🧔‍♂️', - 'woman_beard': '🧔‍♀️', - 'man_red_hair': '👨‍🦰', - 'man_curly_hair': '👨‍🦱', - 'man_white_hair': '👨‍🦳', - 'man_bald': '👨‍🦲', - 'woman': '👩', - 'woman_red_hair': '👩‍🦰', - 'person_red_hair': '🧑‍🦰', - 'woman_curly_hair': '👩‍🦱', - 'person_curly_hair': '🧑‍🦱', - 'woman_white_hair': '👩‍🦳', - 'person_white_hair': '🧑‍🦳', - 'woman_bald': '👩‍🦲', - 'person_bald': '🧑‍🦲', - 'woman_blond_hair': '👱‍♀️', - 'man_blond_hair': '👱‍♂️', - 'older_person': '🧓', - 'old_man': '👴', - 'old_woman': '👵', - 'person_frowning': '🙍', - 'man_frowning': '🙍‍♂️', - 'woman_frowning': '🙍‍♀️', - 'person_pouting': '🙎', - 'man_pouting': '🙎‍♂️', - 'woman_pouting': '🙎‍♀️', - 'person_gesturing_no': '🙅', - 'man_gesturing_no': '🙅‍♂️', - 'woman_gesturing_no': '🙅‍♀️', - 'person_gesturing_ok': '🙆', - 'man_gesturing_ok': '🙆‍♂️', - 'woman_gesturing_ok': '🙆‍♀️', - 'person_tipping_hand': '💁', - 'man_tipping_hand': '💁‍♂️', - 'woman_tipping_hand': '💁‍♀️', - 'person_raising_hand': '🙋', - 'man_raising_hand': '🙋‍♂️', - 'woman_raising_hand': '🙋‍♀️', - 'deaf_person': '🧏', - 'deaf_man': '🧏‍♂️', - 'deaf_woman': '🧏‍♀️', - 'person_bowing': '🙇', - 'man_bowing': '🙇‍♂️', - 'woman_bowing': '🙇‍♀️', - 'person_facepalming': '🤦', - 'man_facepalming': '🤦‍♂️', - 'woman_facepalming': '🤦‍♀️', - 'person_shrugging': '🤷', - 'man_shrugging': '🤷‍♂️', - 'woman_shrugging': '🤷‍♀️', - 'health_worker': '🧑‍⚕️', - 'man_health_worker': '👨‍⚕️', - 'woman_health_worker': '👩‍⚕️', - 'student': '🧑‍🎓', - 'man_student': '👨‍🎓', - 'woman_student': '👩‍🎓', - 'teacher': '🧑‍🏫', - 'man_teacher': '👨‍🏫', - 'woman_teacher': '👩‍🏫', - 'judge': '🧑‍⚖️', - 'man_judge': '👨‍⚖️', - 'woman_judge': '👩‍⚖️', - 'farmer': '🧑‍🌾', - 'man_farmer': '👨‍🌾', - 'woman_farmer': '👩‍🌾', - 'cook': '🧑‍🍳', - 'man_cook': '👨‍🍳', - 'woman_cook': '👩‍🍳', - 'mechanic': '🧑‍🔧', - 'man_mechanic': '👨‍🔧', - 'woman_mechanic': '👩‍🔧', - 'factory_worker': '🧑‍🏭', - 'man_factory_worker': '👨‍🏭', - 'woman_factory_worker': '👩‍🏭', - 'office_worker': '🧑‍💼', - 'man_office_worker': '👨‍💼', - 'woman_office_worker': '👩‍💼', - 'scientist': '🧑‍🔬', - 'man_scientist': '👨‍🔬', - 'woman_scientist': '👩‍🔬', - 'technologist': '🧑‍💻', - 'man_technologist': '👨‍💻', - 'woman_technologist': '👩‍💻', - 'singer': '🧑‍🎤', - 'man_singer': '👨‍🎤', - 'woman_singer': '👩‍🎤', - 'artist': '🧑‍🎨', - 'man_artist': '👨‍🎨', - 'woman_artist': '👩‍🎨', - 'pilot': '🧑‍✈️', - 'man_pilot': '👨‍✈️', - 'woman_pilot': '👩‍✈️', - 'astronaut': '🧑‍🚀', - 'man_astronaut': '👨‍🚀', - 'woman_astronaut': '👩‍🚀', - 'firefighter': '🧑‍🚒', - 'man_firefighter': '👨‍🚒', - 'woman_firefighter': '👩‍🚒', - 'police_officer': '👮', - 'man_police_officer': '👮‍♂️', - 'woman_police_officer': '👮‍♀️', - 'detective': '🕵️', - 'man_detective': '🕵️‍♂️', - 'woman_detective': '🕵️‍♀️', - 'guard': '💂', - 'man_guard': '💂‍♂️', - 'woman_guard': '💂‍♀️', - 'ninja': '🥷', - 'construction_worker': '👷', - 'man_construction_worker': '👷‍♂️', - 'woman_construction_worker': '👷‍♀️', - 'prince': '🤴', - 'princess': '👸', - 'person_wearing_turban': '👳', - 'man_wearing_turban': '👳‍♂️', - 'woman_wearing_turban': '👳‍♀️', - 'person_with_skullcap': '👲', - 'woman_with_headscarf': '🧕', - 'person_in_tuxedo': '🤵', - 'man_in_tuxedo': '🤵‍♂️', - 'woman_in_tuxedo': '🤵‍♀️', - 'person_with_veil': '👰', - 'man_with_veil': '👰‍♂️', - 'woman_with_veil': '👰‍♀️', - 'pregnant_woman': '🤰', - 'breast_feeding': '🤱', - 'woman_feeding_baby': '👩‍🍼', - 'man_feeding_baby': '👨‍🍼', - 'person_feeding_baby': '🧑‍🍼', - 'baby_angel': '👼', - 'santa_claus': '🎅', - 'mrs_claus': '🤶', - 'mx_claus': '🧑‍🎄', - 'superhero': '🦸', - 'man_superhero': '🦸‍♂️', - 'woman_superhero': '🦸‍♀️', - 'supervillain': '🦹', - 'man_supervillain': '🦹‍♂️', - 'woman_supervillain': '🦹‍♀️', - 'mage': '🧙', - 'man_mage': '🧙‍♂️', - 'woman_mage': '🧙‍♀️', - 'fairy': '🧚', - 'man_fairy': '🧚‍♂️', - 'woman_fairy': '🧚‍♀️', - 'vampire': '🧛', - 'man_vampire': '🧛‍♂️', - 'woman_vampire': '🧛‍♀️', - 'merperson': '🧜', - 'merman': '🧜‍♂️', - 'mermaid': '🧜‍♀️', - 'elf': '🧝', - 'man_elf': '🧝‍♂️', - 'woman_elf': '🧝‍♀️', - 'genie': '🧞', - 'man_genie': '🧞‍♂️', - 'woman_genie': '🧞‍♀️', - 'zombie': '🧟', - 'man_zombie': '🧟‍♂️', - 'woman_zombie': '🧟‍♀️', - 'person_getting_massage': '💆', - 'man_getting_massage': '💆‍♂️', - 'woman_getting_massage': '💆‍♀️', - 'person_getting_haircut': '💇', - 'man_getting_haircut': '💇‍♂️', - 'woman_getting_haircut': '💇‍♀️', - 'person_walking': '🚶', - 'man_walking': '🚶‍♂️', - 'woman_walking': '🚶‍♀️', - 'person_standing': '🧍', - 'man_standing': '🧍‍♂️', - 'woman_standing': '🧍‍♀️', - 'person_kneeling': '🧎', - 'man_kneeling': '🧎‍♂️', - 'woman_kneeling': '🧎‍♀️', - 'person_with_white_cane': '🧑‍🦯', - 'man_with_white_cane': '👨‍🦯', - 'woman_with_white_cane': '👩‍🦯', - 'person_in_motorized_wheelchair': '🧑‍🦼', - 'man_in_motorized_wheelchair': '👨‍🦼', - 'woman_in_motorized_wheelchair': '👩‍🦼', - 'person_in_manual_wheelchair': '🧑‍🦽', - 'man_in_manual_wheelchair': '👨‍🦽', - 'woman_in_manual_wheelchair': '👩‍🦽', - 'person_running': '🏃', - 'man_running': '🏃‍♂️', - 'woman_running': '🏃‍♀️', - 'woman_dancing': '💃', - 'man_dancing': '🕺', - 'person_in_suit_levitating': '🕴️', - 'people_with_bunny_ears': '👯', - 'men_with_bunny_ears': '👯‍♂️', - 'women_with_bunny_ears': '👯‍♀️', - 'person_in_steamy_room': '🧖', - 'man_in_steamy_room': '🧖‍♂️', - 'woman_in_steamy_room': '🧖‍♀️', - 'person_climbing': '🧗', - 'man_climbing': '🧗‍♂️', - 'woman_climbing': '🧗‍♀️', - 'person_fencing': '🤺', - 'horse_racing': '🏇', - 'skier': '⛷️', - 'snowboarder': '🏂', - 'person_golfing': '🏌️', - 'man_golfing': '🏌️‍♂️', - 'woman_golfing': '🏌️‍♀️', - 'person_surfing': '🏄', - 'man_surfing': '🏄‍♂️', - 'woman_surfing': '🏄‍♀️', - 'person_rowing_boat': '🚣', - 'man_rowing_boat': '🚣‍♂️', - 'woman_rowing_boat': '🚣‍♀️', - 'person_swimming': '🏊', - 'man_swimming': '🏊‍♂️', - 'woman_swimming': '🏊‍♀️', - 'person_bouncing_ball': '⛹️', - 'man_bouncing_ball': '⛹️‍♂️', - 'woman_bouncing_ball': '⛹️‍♀️', - 'person_lifting_weights': '🏋️', - 'man_lifting_weights': '🏋️‍♂️', - 'woman_lifting_weights': '🏋️‍♀️', - 'person_biking': '🚴', - 'man_biking': '🚴‍♂️', - 'woman_biking': '🚴‍♀️', - 'person_mountain_biking': '🚵', - 'man_mountain_biking': '🚵‍♂️', - 'woman_mountain_biking': '🚵‍♀️', - 'person_cartwheeling': '🤸', - 'man_cartwheeling': '🤸‍♂️', - 'woman_cartwheeling': '🤸‍♀️', - 'people_wrestling': '🤼', - 'men_wrestling': '🤼‍♂️', - 'women_wrestling': '🤼‍♀️', - 'person_playing_water_polo': '🤽', - 'man_playing_water_polo': '🤽‍♂️', - 'woman_playing_water_polo': '🤽‍♀️', - 'person_playing_handball': '🤾', - 'man_playing_handball': '🤾‍♂️', - 'woman_playing_handball': '🤾‍♀️', - 'person_juggling': '🤹', - 'man_juggling': '🤹‍♂️', - 'woman_juggling': '🤹‍♀️', - 'person_in_lotus_position': '🧘', - 'man_in_lotus_position': '🧘‍♂️', - 'woman_in_lotus_position': '🧘‍♀️', - 'person_taking_bath': '🛀', - 'person_in_bed': '🛌', - 'people_holding_hands': '🧑‍🤝‍🧑', - 'women_holding_hands': '👭', - 'woman_and_man_holding_hands': '👫', - 'men_holding_hands': '👬', - 'kiss': '💏', - 'kiss_woman_man': '👩‍❤️‍💋‍👨', - 'kiss_man_man': '👨‍❤️‍💋‍👨', - 'kiss_woman_woman': '👩‍❤️‍💋‍👩', - 'couple_with_heart': '💑', - 'couple_with_heart_woman_man': '👩‍❤️‍👨', - 'couple_with_heart_man_man': '👨‍❤️‍👨', - 'couple_with_heart_woman_woman': '👩‍❤️‍👩', - 'family': '👪', - 'family_man_woman_boy': '👨‍👩‍👦', - 'family_man_woman_girl': '👨‍👩‍👧', - 'family_man_woman_girl_boy': '👨‍👩‍👧‍👦', - 'family_man_woman_boy_boy': '👨‍👩‍👦‍👦', - 'family_man_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_woman_boy': '👩‍👩‍👦', - 'family_woman_woman_girl': '👩‍👩‍👧', - 'family_woman_woman_girl_boy': '👩‍👩‍👧‍👦', - 'family_woman_woman_boy_boy': '👩‍👩‍👦‍👦', - 'family_woman_woman_girl_girl': '👩‍👩‍👧‍👧', - 'family_man_boy': '👨‍👦', - 'family_man_boy_boy': '👨‍👦‍👦', - 'family_man_girl': '👨‍👧', - 'family_man_girl_boy': '👨‍👧‍👦', - 'family_man_girl_girl': '👨‍👧‍👧', - 'family_woman_boy': '👩‍👦', - 'family_woman_boy_boy': '👩‍👦‍👦', - 'family_woman_girl': '👩‍👧', - 'family_woman_girl_boy': '👩‍👧‍👦', - 'family_woman_girl_girl': '👩‍👧‍👧', - 'speaking_head': '🗣️', - 'bust_in_silhouette': '👤', - 'busts_in_silhouette': '👥', - 'people_hugging': '🫂', - 'footprints': '👣', - 'monkey_face': '🐵', - 'monkey': '🐒', - 'gorilla': '🦍', - 'orangutan': '🦧', - 'dog_face': '🐶', - 'dog': '🐕', - 'guide_dog': '🦮', - 'service_dog': '🐕‍🦺', - 'poodle': '🐩', - 'wolf': '🐺', - 'fox': '🦊', - 'raccoon': '🦝', - 'cat_face': '🐱', - 'cat': '🐈', - 'black_cat': '🐈‍⬛', - 'lion': '🦁', - 'tiger_face': '🐯', - 'tiger': '🐅', - 'leopard': '🐆', - 'horse_face': '🐴', - 'horse': '🐎', - 'unicorn': '🦄', - 'zebra': '🦓', - 'deer': '🦌', - 'bison': '🦬', - 'cow_face': '🐮', - 'ox': '🐂', - 'water_buffalo': '🐃', - 'cow': '🐄', - 'pig_face': '🐷', - 'pig': '🐖', - 'boar': '🐗', - 'pig_nose': '🐽', - 'ram': '🐏', - 'ewe': '🐑', - 'goat': '🐐', - 'camel': '🐪', - 'two_hump_camel': '🐫', - 'llama': '🦙', - 'giraffe': '🦒', - 'elephant': '🐘', - 'mammoth': '🦣', - 'rhinoceros': '🦏', - 'hippopotamus': '🦛', - 'mouse_face': '🐭', - 'mouse': '🐁', - 'rat': '🐀', - 'hamster': '🐹', - 'rabbit_face': '🐰', - 'rabbit': '🐇', - 'chipmunk': '🐿️', - 'beaver': '🦫', - 'hedgehog': '🦔', - 'bat': '🦇', - 'bear': '🐻', - 'polar_bear': '🐻‍❄️', - 'koala': '🐨', - 'panda': '🐼', - 'sloth': '🦥', - 'otter': '🦦', - 'skunk': '🦨', - 'kangaroo': '🦘', - 'badger': '🦡', - 'paw_prints': '🐾', - 'turkey': '🦃', - 'chicken': '🐔', - 'rooster': '🐓', - 'hatching_chick': '🐣', - 'baby_chick': '🐤', - 'front_facing_baby_chick': '🐥', - 'bird': '🐦', - 'penguin': '🐧', - 'dove': '🕊️', - 'eagle': '🦅', - 'duck': '🦆', - 'swan': '🦢', - 'owl': '🦉', - 'dodo': '🦤', - 'feather': '🪶', - 'flamingo': '🦩', - 'peacock': '🦚', - 'parrot': '🦜', - 'frog': '🐸', - 'crocodile': '🐊', - 'turtle': '🐢', - 'lizard': '🦎', - 'snake': '🐍', - 'dragon_face': '🐲', - 'dragon': '🐉', - 'sauropod': '🦕', - 't_rex': '🦖', - 'spouting_whale': '🐳', - 'whale': '🐋', - 'dolphin': '🐬', - 'seal': '🦭', - 'fish': '🐟', - 'tropical_fish': '🐠', - 'blowfish': '🐡', - 'shark': '🦈', - 'octopus': '🐙', - 'spiral_shell': '🐚', - 'snail': '🐌', - 'butterfly': '🦋', - 'bug': '🐛', - 'ant': '🐜', - 'honeybee': '🐝', - 'beetle': '🪲', - 'lady_beetle': '🐞', - 'cricket': '🦗', - 'cockroach': '🪳', - 'spider': '🕷️', - 'spider_web': '🕸️', - 'scorpion': '🦂', - 'mosquito': '🦟', - 'fly': '🪰', - 'worm': '🪱', - 'microbe': '🦠', - 'bouquet': '💐', - 'cherry_blossom': '🌸', - 'white_flower': '💮', - 'rosette': '🏵️', - 'rose': '🌹', - 'wilted_flower': '🥀', - 'hibiscus': '🌺', - 'sunflower': '🌻', - 'blossom': '🌼', - 'tulip': '🌷', - 'seedling': '🌱', - 'potted_plant': '🪴', - 'evergreen_tree': '🌲', - 'deciduous_tree': '🌳', - 'palm_tree': '🌴', - 'cactus': '🌵', - 'sheaf_of_rice': '🌾', - 'herb': '🌿', - 'shamrock': '☘️', - 'four_leaf_clover': '🍀', - 'maple_leaf': '🍁', - 'fallen_leaf': '🍂', - 'leaf_fluttering_in_wind': '🍃', - 'grapes': '🍇', - 'melon': '🍈', - 'watermelon': '🍉', - 'tangerine': '🍊', - 'lemon': '🍋', - 'banana': '🍌', - 'pineapple': '🍍', - 'mango': '🥭', - 'red_apple': '🍎', - 'green_apple': '🍏', - 'pear': '🍐', - 'peach': '🍑', - 'cherries': '🍒', - 'strawberry': '🍓', - 'blueberries': '🫐', - 'kiwi_fruit': '🥝', - 'tomato': '🍅', - 'olive': '🫒', - 'coconut': '🥥', - 'avocado': '🥑', - 'eggplant': '🍆', - 'potato': '🥔', - 'carrot': '🥕', - 'ear_of_corn': '🌽', - 'hot_pepper': '🌶️', - 'bell_pepper': '🫑', - 'cucumber': '🥒', - 'leafy_green': '🥬', - 'broccoli': '🥦', - 'garlic': '🧄', - 'onion': '🧅', - 'mushroom': '🍄', - 'peanuts': '🥜', - 'chestnut': '🌰', - 'bread': '🍞', - 'croissant': '🥐', - 'baguette_bread': '🥖', - 'flatbread': '🫓', - 'pretzel': '🥨', - 'bagel': '🥯', - 'pancakes': '🥞', - 'waffle': '🧇', - 'cheese_wedge': '🧀', - 'meat_on_bone': '🍖', - 'poultry_leg': '🍗', - 'cut_of_meat': '🥩', - 'bacon': '🥓', - 'hamburger': '🍔', - 'french_fries': '🍟', - 'pizza': '🍕', - 'hot_dog': '🌭', - 'sandwich': '🥪', - 'taco': '🌮', - 'burrito': '🌯', - 'tamale': '🫔', - 'stuffed_flatbread': '🥙', - 'falafel': '🧆', - 'egg': '🥚', - 'cooking': '🍳', - 'shallow_pan_of_food': '🥘', - 'pot_of_food': '🍲', - 'fondue': '🫕', - 'bowl_with_spoon': '🥣', - 'green_salad': '🥗', - 'popcorn': '🍿', - 'butter': '🧈', - 'salt': '🧂', - 'canned_food': '🥫', - 'bento_box': '🍱', - 'rice_cracker': '🍘', - 'rice_ball': '🍙', - 'cooked_rice': '🍚', - 'curry_rice': '🍛', - 'steaming_bowl': '🍜', - 'spaghetti': '🍝', - 'roasted_sweet_potato': '🍠', - 'oden': '🍢', - 'sushi': '🍣', - 'fried_shrimp': '🍤', - 'fish_cake_with_swirl': '🍥', - 'moon_cake': '🥮', - 'dango': '🍡', - 'dumpling': '🥟', - 'fortune_cookie': '🥠', - 'takeout_box': '🥡', - 'crab': '🦀', - 'lobster': '🦞', - 'shrimp': '🦐', - 'squid': '🦑', - 'oyster': '🦪', - 'soft_ice_cream': '🍦', - 'shaved_ice': '🍧', - 'ice_cream': '🍨', - 'doughnut': '🍩', - 'cookie': '🍪', - 'birthday_cake': '🎂', - 'shortcake': '🍰', - 'cupcake': '🧁', - 'pie': '🥧', - 'chocolate_bar': '🍫', - 'candy': '🍬', - 'lollipop': '🍭', - 'custard': '🍮', - 'honey_pot': '🍯', - 'baby_bottle': '🍼', - 'glass_of_milk': '🥛', - 'hot_beverage': '☕', - 'teapot': '🫖', - 'teacup_without_handle': '🍵', - 'sake': '🍶', - 'bottle_with_popping_cork': '🍾', - 'wine_glass': '🍷', - 'cocktail_glass': '🍸', - 'tropical_drink': '🍹', - 'beer_mug': '🍺', - 'clinking_beer_mugs': '🍻', - 'clinking_glasses': '🥂', - 'tumbler_glass': '🥃', - 'cup_with_straw': '🥤', - 'bubble_tea': '🧋', - 'beverage_box': '🧃', - 'mate': '🧉', - 'ice': '🧊', - 'chopsticks': '🥢', - 'fork_and_knife_with_plate': '🍽️', - 'fork_and_knife': '🍴', - 'spoon': '🥄', - 'kitchen_knife': '🔪', - 'amphora': '🏺', - 'globe_showing_europe_africa': '🌍', - 'globe_showing_americas': '🌎', - 'globe_showing_asia_australia': '🌏', - 'globe_with_meridians': '🌐', - 'world_map': '🗺️', - 'map_of_japan': '🗾', - 'compass': '🧭', - 'snow_capped_mountain': '🏔️', - 'mountain': '⛰️', - 'volcano': '🌋', - 'mount_fuji': '🗻', - 'camping': '🏕️', - 'beach_with_umbrella': '🏖️', - 'desert': '🏜️', - 'desert_island': '🏝️', - 'national_park': '🏞️', - 'stadium': '🏟️', - 'classical_building': '🏛️', - 'building_construction': '🏗️', - 'brick': '🧱', - 'rock': '🪨', - 'wood': '🪵', - 'hut': '🛖', - 'houses': '🏘️', - 'derelict_house': '🏚️', - 'house': '🏠', - 'house_with_garden': '🏡', - 'office_building': '🏢', - 'japanese_post_office': '🏣', - 'post_office': '🏤', - 'hospital': '🏥', - 'bank': '🏦', - 'hotel': '🏨', - 'love_hotel': '🏩', - 'convenience_store': '🏪', - 'school': '🏫', - 'department_store': '🏬', - 'factory': '🏭', - 'japanese_castle': '🏯', - 'castle': '🏰', - 'wedding': '💒', - 'tokyo_tower': '🗼', - 'statue_of_liberty': '🗽', - 'church': '⛪', - 'mosque': '🕌', - 'hindu_temple': '🛕', - 'synagogue': '🕍', - 'shinto_shrine': '⛩️', - 'kaaba': '🕋', - 'fountain': '⛲', - 'tent': '⛺', - 'foggy': '🌁', - 'night_with_stars': '🌃', - 'cityscape': '🏙️', - 'sunrise_over_mountains': '🌄', - 'sunrise': '🌅', - 'cityscape_at_dusk': '🌆', - 'sunset': '🌇', - 'bridge_at_night': '🌉', - 'hot_springs': '♨️', - 'carousel_horse': '🎠', - 'ferris_wheel': '🎡', - 'roller_coaster': '🎢', - 'barber_pole': '💈', - 'circus_tent': '🎪', - 'locomotive': '🚂', - 'railway_car': '🚃', - 'high_speed_train': '🚄', - 'bullet_train': '🚅', - 'train': '🚆', - 'metro': '🚇', - 'light_rail': '🚈', - 'station': '🚉', - 'tram': '🚊', - 'monorail': '🚝', - 'mountain_railway': '🚞', - 'tram_car': '🚋', - 'bus': '🚌', - 'oncoming_bus': '🚍', - 'trolleybus': '🚎', - 'minibus': '🚐', - 'ambulance': '🚑', - 'fire_engine': '🚒', - 'police_car': '🚓', - 'oncoming_police_car': '🚔', - 'taxi': '🚕', - 'oncoming_taxi': '🚖', - 'automobile': '🚗', - 'oncoming_automobile': '🚘', - 'sport_utility_vehicle': '🚙', - 'pickup_truck': '🛻', - 'delivery_truck': '🚚', - 'articulated_lorry': '🚛', - 'tractor': '🚜', - 'racing_car': '🏎️', - 'motorcycle': '🏍️', - 'motor_scooter': '🛵', - 'manual_wheelchair': '🦽', - 'motorized_wheelchair': '🦼', - 'auto_rickshaw': '🛺', - 'bicycle': '🚲', - 'kick_scooter': '🛴', - 'skateboard': '🛹', - 'roller_skate': '🛼', - 'bus_stop': '🚏', - 'motorway': '🛣️', - 'railway_track': '🛤️', - 'oil_drum': '🛢️', - 'fuel_pump': '⛽', - 'police_car_light': '🚨', - 'horizontal_traffic_light': '🚥', - 'vertical_traffic_light': '🚦', - 'stop_sign': '🛑', - 'construction': '🚧', - 'anchor': '⚓', - 'sailboat': '⛵', - 'canoe': '🛶', - 'speedboat': '🚤', - 'passenger_ship': '🛳️', - 'ferry': '⛴️', - 'motor_boat': '🛥️', - 'ship': '🚢', - 'airplane': '✈️', - 'small_airplane': '🛩️', - 'airplane_departure': '🛫', - 'airplane_arrival': '🛬', - 'parachute': '🪂', - 'seat': '💺', - 'helicopter': '🚁', - 'suspension_railway': '🚟', - 'mountain_cableway': '🚠', - 'aerial_tramway': '🚡', - 'satellite': '🛰️', - 'rocket': '🚀', - 'flying_saucer': '🛸', - 'bellhop_bell': '🛎️', - 'luggage': '🧳', - 'hourglass_done': '⌛', - 'hourglass_not_done': '⏳', - 'watch': '⌚', - 'alarm_clock': '⏰', - 'stopwatch': '⏱️', - 'timer_clock': '⏲️', - 'mantelpiece_clock': '🕰️', - 'twelve_o_clock': '🕛', - 'twelve_thirty': '🕧', - 'one_o_clock': '🕐', - 'one_thirty': '🕜', - 'two_o_clock': '🕑', - 'two_thirty': '🕝', - 'three_o_clock': '🕒', - 'three_thirty': '🕞', - 'four_o_clock': '🕓', - 'four_thirty': '🕟', - 'five_o_clock': '🕔', - 'five_thirty': '🕠', - 'six_o_clock': '🕕', - 'six_thirty': '🕡', - 'seven_o_clock': '🕖', - 'seven_thirty': '🕢', - 'eight_o_clock': '🕗', - 'eight_thirty': '🕣', - 'nine_o_clock': '🕘', - 'nine_thirty': '🕤', - 'ten_o_clock': '🕙', - 'ten_thirty': '🕥', - 'eleven_o_clock': '🕚', - 'eleven_thirty': '🕦', - 'new_moon': '🌑', - 'waxing_crescent_moon': '🌒', - 'first_quarter_moon': '🌓', - 'waxing_gibbous_moon': '🌔', - 'full_moon': '🌕', - 'waning_gibbous_moon': '🌖', - 'last_quarter_moon': '🌗', - 'waning_crescent_moon': '🌘', - 'crescent_moon': '🌙', - 'new_moon_face': '🌚', - 'first_quarter_moon_face': '🌛', - 'last_quarter_moon_face': '🌜', - 'thermometer': '🌡️', - 'sun': '☀️', - 'full_moon_face': '🌝', - 'sun_with_face': '🌞', - 'ringed_planet': '🪐', - 'star': '⭐', - 'glowing_star': '🌟', - 'shooting_star': '🌠', - 'milky_way': '🌌', - 'cloud': '☁️', - 'sun_behind_cloud': '⛅', - 'cloud_with_lightning_and_rain': '⛈️', - 'sun_behind_small_cloud': '🌤️', - 'sun_behind_large_cloud': '🌥️', - 'sun_behind_rain_cloud': '🌦️', - 'cloud_with_rain': '🌧️', - 'cloud_with_snow': '🌨️', - 'cloud_with_lightning': '🌩️', - 'tornado': '🌪️', - 'fog': '🌫️', - 'wind_face': '🌬️', - 'cyclone': '🌀', - 'rainbow': '🌈', - 'closed_umbrella': '🌂', - 'umbrella': '☂️', - 'umbrella_with_rain_drops': '☔', - 'umbrella_on_ground': '⛱️', - 'high_voltage': '⚡', - 'snowflake': '❄️', - 'snowman': '☃️', - 'snowman_without_snow': '⛄', - 'comet': '☄️', - 'fire': '🔥', - 'droplet': '💧', - 'water_wave': '🌊', - 'jack_o_lantern': '🎃', - 'christmas_tree': '🎄', - 'fireworks': '🎆', - 'sparkler': '🎇', - 'firecracker': '🧨', - 'sparkles': '✨', - 'balloon': '🎈', - 'party_popper': '🎉', - 'confetti_ball': '🎊', - 'tanabata_tree': '🎋', - 'pine_decoration': '🎍', - 'japanese_dolls': '🎎', - 'carp_streamer': '🎏', - 'wind_chime': '🎐', - 'moon_viewing_ceremony': '🎑', - 'red_envelope': '🧧', - 'ribbon': '🎀', - 'wrapped_gift': '🎁', - 'reminder_ribbon': '🎗️', - 'admission_tickets': '🎟️', - 'ticket': '🎫', - 'military_medal': '🎖️', - 'trophy': '🏆', - 'sports_medal': '🏅', - '1st_place_medal': '🥇', - '2nd_place_medal': '🥈', - '3rd_place_medal': '🥉', - 'soccer_ball': '⚽', - 'baseball': '⚾', - 'softball': '🥎', - 'basketball': '🏀', - 'volleyball': '🏐', - 'american_football': '🏈', - 'rugby_football': '🏉', - 'tennis': '🎾', - 'flying_disc': '🥏', - 'bowling': '🎳', - 'cricket_game': '🏏', - 'field_hockey': '🏑', - 'ice_hockey': '🏒', - 'lacrosse': '🥍', - 'ping_pong': '🏓', - 'badminton': '🏸', - 'boxing_glove': '🥊', - 'martial_arts_uniform': '🥋', - 'goal_net': '🥅', - 'flag_in_hole': '⛳', - 'ice_skate': '⛸️', - 'fishing_pole': '🎣', - 'diving_mask': '🤿', - 'running_shirt': '🎽', - 'skis': '🎿', - 'sled': '🛷', - 'curling_stone': '🥌', - 'bullseye': '🎯', - 'yo_yo': '🪀', - 'kite': '🪁', - 'pool_8_ball': '🎱', - 'crystal_ball': '🔮', - 'magic_wand': '🪄', - 'nazar_amulet': '🧿', - 'video_game': '🎮', - 'joystick': '🕹️', - 'slot_machine': '🎰', - 'game_die': '🎲', - 'puzzle_piece': '🧩', - 'teddy_bear': '🧸', - 'pinata': '🪅', - 'nesting_dolls': '🪆', - 'spade_suit': '♠️', - 'heart_suit': '♥️', - 'diamond_suit': '♦️', - 'club_suit': '♣️', - 'chess_pawn': '♟️', - 'joker': '🃏', - 'mahjong_red_dragon': '🀄', - 'flower_playing_cards': '🎴', - 'performing_arts': '🎭', - 'framed_picture': '🖼️', - 'artist_palette': '🎨', - 'thread': '🧵', - 'sewing_needle': '🪡', - 'yarn': '🧶', - 'knot': '🪢', - 'glasses': '👓', - 'sunglasses': '🕶️', - 'goggles': '🥽', - 'lab_coat': '🥼', - 'safety_vest': '🦺', - 'necktie': '👔', - 't_shirt': '👕', - 'jeans': '👖', - 'scarf': '🧣', - 'gloves': '🧤', - 'coat': '🧥', - 'socks': '🧦', - 'dress': '👗', - 'kimono': '👘', - 'sari': '🥻', - 'one_piece_swimsuit': '🩱', - 'briefs': '🩲', - 'shorts': '🩳', - 'bikini': '👙', - 'woman_s_clothes': '👚', - 'purse': '👛', - 'handbag': '👜', - 'clutch_bag': '👝', - 'shopping_bags': '🛍️', - 'backpack': '🎒', - 'thong_sandal': '🩴', - 'man_s_shoe': '👞', - 'running_shoe': '👟', - 'hiking_boot': '🥾', - 'flat_shoe': '🥿', - 'high_heeled_shoe': '👠', - 'woman_s_sandal': '👡', - 'ballet_shoes': '🩰', - 'woman_s_boot': '👢', - 'crown': '👑', - 'woman_s_hat': '👒', - 'top_hat': '🎩', - 'graduation_cap': '🎓', - 'billed_cap': '🧢', - 'military_helmet': '🪖', - 'rescue_worker_s_helmet': '⛑️', - 'prayer_beads': '📿', - 'lipstick': '💄', - 'ring': '💍', - 'gem_stone': '💎', - 'muted_speaker': '🔇', - 'speaker_low_volume': '🔈', - 'speaker_medium_volume': '🔉', - 'speaker_high_volume': '🔊', - 'loudspeaker': '📢', - 'megaphone': '📣', - 'postal_horn': '📯', - 'bell': '🔔', - 'bell_with_slash': '🔕', - 'musical_score': '🎼', - 'musical_note': '🎵', - 'musical_notes': '🎶', - 'studio_microphone': '🎙️', - 'level_slider': '🎚️', - 'control_knobs': '🎛️', - 'microphone': '🎤', - 'headphone': '🎧', - 'radio': '📻', - 'saxophone': '🎷', - 'accordion': '🪗', - 'guitar': '🎸', - 'musical_keyboard': '🎹', - 'trumpet': '🎺', - 'violin': '🎻', - 'banjo': '🪕', - 'drum': '🥁', - 'long_drum': '🪘', - 'mobile_phone': '📱', - 'mobile_phone_with_arrow': '📲', - 'telephone': '☎️', - 'telephone_receiver': '📞', - 'pager': '📟', - 'fax_machine': '📠', - 'battery': '🔋', - 'electric_plug': '🔌', - 'laptop': '💻', - 'desktop_computer': '🖥️', - 'printer': '🖨️', - 'keyboard': '⌨️', - 'computer_mouse': '🖱️', - 'trackball': '🖲️', - 'computer_disk': '💽', - 'floppy_disk': '💾', - 'optical_disk': '💿', - 'dvd': '📀', - 'abacus': '🧮', - 'movie_camera': '🎥', - 'film_frames': '🎞️', - 'film_projector': '📽️', - 'clapper_board': '🎬', - 'television': '📺', - 'camera': '📷', - 'camera_with_flash': '📸', - 'video_camera': '📹', - 'videocassette': '📼', - 'magnifying_glass_tilted_left': '🔍', - 'magnifying_glass_tilted_right': '🔎', - 'candle': '🕯️', - 'light_bulb': '💡', - 'flashlight': '🔦', - 'red_paper_lantern': '🏮', - 'diya_lamp': '🪔', - 'notebook_with_decorative_cover': '📔', - 'closed_book': '📕', - 'open_book': '📖', - 'green_book': '📗', - 'blue_book': '📘', - 'orange_book': '📙', - 'books': '📚', - 'notebook': '📓', - 'ledger': '📒', - 'page_with_curl': '📃', - 'scroll': '📜', - 'page_facing_up': '📄', - 'newspaper': '📰', - 'rolled_up_newspaper': '🗞️', - 'bookmark_tabs': '📑', - 'bookmark': '🔖', - 'label': '🏷️', - 'money_bag': '💰', - 'coin': '🪙', - 'yen_banknote': '💴', - 'dollar_banknote': '💵', - 'euro_banknote': '💶', - 'pound_banknote': '💷', - 'money_with_wings': '💸', - 'credit_card': '💳', - 'receipt': '🧾', - 'chart_increasing_with_yen': '💹', - 'envelope': '✉️', - 'e_mail': '📧', - 'incoming_envelope': '📨', - 'envelope_with_arrow': '📩', - 'outbox_tray': '📤', - 'inbox_tray': '📥', - 'package': '📦', - 'closed_mailbox_with_raised_flag': '📫', - 'closed_mailbox_with_lowered_flag': '📪', - 'open_mailbox_with_raised_flag': '📬', - 'open_mailbox_with_lowered_flag': '📭', - 'postbox': '📮', - 'ballot_box_with_ballot': '🗳️', - 'pencil': '✏️', - 'black_nib': '✒️', - 'fountain_pen': '🖋️', - 'pen': '🖊️', - 'paintbrush': '🖌️', - 'crayon': '🖍️', - 'memo': '📝', - 'briefcase': '💼', - 'file_folder': '📁', - 'open_file_folder': '📂', - 'card_index_dividers': '🗂️', - 'calendar': '📅', - 'tear_off_calendar': '📆', - 'spiral_notepad': '🗒️', - 'spiral_calendar': '🗓️', - 'card_index': '📇', - 'chart_increasing': '📈', - 'chart_decreasing': '📉', - 'bar_chart': '📊', - 'clipboard': '📋', - 'pushpin': '📌', - 'round_pushpin': '📍', - 'paperclip': '📎', - 'linked_paperclips': '🖇️', - 'straight_ruler': '📏', - 'triangular_ruler': '📐', - 'scissors': '✂️', - 'card_file_box': '🗃️', - 'file_cabinet': '🗄️', - 'wastebasket': '🗑️', - 'locked': '🔒', - 'unlocked': '🔓', - 'locked_with_pen': '🔏', - 'locked_with_key': '🔐', - 'key': '🔑', - 'old_key': '🗝️', - 'hammer': '🔨', - 'axe': '🪓', - 'pick': '⛏️', - 'hammer_and_pick': '⚒️', - 'hammer_and_wrench': '🛠️', - 'dagger': '🗡️', - 'crossed_swords': '⚔️', - 'water_pistol': '🔫', - 'boomerang': '🪃', - 'bow_and_arrow': '🏹', - 'shield': '🛡️', - 'carpentry_saw': '🪚', - 'wrench': '🔧', - 'screwdriver': '🪛', - 'nut_and_bolt': '🔩', - 'gear': '⚙️', - 'clamp': '🗜️', - 'balance_scale': '⚖️', - 'white_cane': '🦯', - 'link': '🔗', - 'chains': '⛓️', - 'hook': '🪝', - 'toolbox': '🧰', - 'magnet': '🧲', - 'ladder': '🪜', - 'alembic': '⚗️', - 'test_tube': '🧪', - 'petri_dish': '🧫', - 'dna': '🧬', - 'microscope': '🔬', - 'telescope': '🔭', - 'satellite_antenna': '📡', - 'syringe': '💉', - 'drop_of_blood': '🩸', - 'pill': '💊', - 'adhesive_bandage': '🩹', - 'stethoscope': '🩺', - 'door': '🚪', - 'elevator': '🛗', - 'mirror': '🪞', - 'window': '🪟', - 'bed': '🛏️', - 'couch_and_lamp': '🛋️', - 'chair': '🪑', - 'toilet': '🚽', - 'plunger': '🪠', - 'shower': '🚿', - 'bathtub': '🛁', - 'mouse_trap': '🪤', - 'razor': '🪒', - 'lotion_bottle': '🧴', - 'safety_pin': '🧷', - 'broom': '🧹', - 'basket': '🧺', - 'roll_of_paper': '🧻', - 'bucket': '🪣', - 'soap': '🧼', - 'toothbrush': '🪥', - 'sponge': '🧽', - 'fire_extinguisher': '🧯', - 'shopping_cart': '🛒', - 'cigarette': '🚬', - 'coffin': '⚰️', - 'headstone': '🪦', - 'funeral_urn': '⚱️', - 'moai': '🗿', - 'placard': '🪧', - 'atm_sign': '🏧', - 'litter_in_bin_sign': '🚮', - 'potable_water': '🚰', - 'wheelchair_symbol': '♿', - 'men_s_room': '🚹', - 'women_s_room': '🚺', - 'restroom': '🚻', - 'baby_symbol': '🚼', - 'water_closet': '🚾', - 'passport_control': '🛂', - 'customs': '🛃', - 'baggage_claim': '🛄', - 'left_luggage': '🛅', - 'warning': '⚠️', - 'children_crossing': '🚸', - 'no_entry': '⛔', - 'prohibited': '🚫', - 'no_bicycles': '🚳', - 'no_smoking': '🚭', - 'no_littering': '🚯', - 'non_potable_water': '🚱', - 'no_pedestrians': '🚷', - 'no_mobile_phones': '📵', - 'no_one_under_eighteen': '🔞', - 'radioactive': '☢️', - 'biohazard': '☣️', - 'up_arrow': '⬆️', - 'up_right_arrow': '↗️', - 'right_arrow': '➡️', - 'down_right_arrow': '↘️', - 'down_arrow': '⬇️', - 'down_left_arrow': '↙️', - 'left_arrow': '⬅️', - 'up_left_arrow': '↖️', - 'up_down_arrow': '↕️', - 'left_right_arrow': '↔️', - 'right_arrow_curving_left': '↩️', - 'left_arrow_curving_right': '↪️', - 'right_arrow_curving_up': '⤴️', - 'right_arrow_curving_down': '⤵️', - 'clockwise_vertical_arrows': '🔃', - 'counterclockwise_arrows_button': '🔄', - 'back_arrow': '🔙', - 'end_arrow': '🔚', - 'on_arrow': '🔛', - 'soon_arrow': '🔜', - 'top_arrow': '🔝', - 'place_of_worship': '🛐', - 'atom_symbol': '⚛️', - 'om': '🕉️', - 'star_of_david': '✡️', - 'wheel_of_dharma': '☸️', - 'yin_yang': '☯️', - 'latin_cross': '✝️', - 'orthodox_cross': '☦️', - 'star_and_crescent': '☪️', - 'peace_symbol': '☮️', - 'menorah': '🕎', - 'dotted_six_pointed_star': '🔯', - 'aries': '♈', - 'taurus': '♉', - 'gemini': '♊', - 'cancer': '♋', - 'leo': '♌', - 'virgo': '♍', - 'libra': '♎', - 'scorpio': '♏', - 'sagittarius': '♐', - 'capricorn': '♑', - 'aquarius': '♒', - 'pisces': '♓', - 'ophiuchus': '⛎', - 'shuffle_tracks_button': '🔀', - 'repeat_button': '🔁', - 'repeat_single_button': '🔂', - 'play_button': '▶️', - 'fast_forward_button': '⏩', - 'next_track_button': '⏭️', - 'play_or_pause_button': '⏯️', - 'reverse_button': '◀️', - 'fast_reverse_button': '⏪', - 'last_track_button': '⏮️', - 'upwards_button': '🔼', - 'fast_up_button': '⏫', - 'downwards_button': '🔽', - 'fast_down_button': '⏬', - 'pause_button': '⏸️', - 'stop_button': '⏹️', - 'record_button': '⏺️', - 'eject_button': '⏏️', - 'cinema': '🎦', - 'dim_button': '🔅', - 'bright_button': '🔆', - 'antenna_bars': '📶', - 'vibration_mode': '📳', - 'mobile_phone_off': '📴', - 'female_sign': '♀️', - 'male_sign': '♂️', - 'transgender_symbol': '⚧️', - 'multiply': '✖️', - 'plus': '➕', - 'minus': '➖', - 'divide': '➗', - 'infinity': '♾️', - 'double_exclamation_mark': '‼️', - 'exclamation_question_mark': '⁉️', - 'red_question_mark': '❓', - 'white_question_mark': '❔', - 'white_exclamation_mark': '❕', - 'red_exclamation_mark': '❗', - 'wavy_dash': '〰️', - 'currency_exchange': '💱', - 'heavy_dollar_sign': '💲', - 'medical_symbol': '⚕️', - 'recycling_symbol': '♻️', - 'fleur_de_lis': '⚜️', - 'trident_emblem': '🔱', - 'name_badge': '📛', - 'japanese_symbol_for_beginner': '🔰', - 'hollow_red_circle': '⭕', - 'check_mark_button': '✅', - 'check_box_with_check': '☑️', - 'check_mark': '✔️', - 'cross_mark': '❌', - 'cross_mark_button': '❎', - 'curly_loop': '➰', - 'double_curly_loop': '➿', - 'part_alternation_mark': '〽️', - 'eight_spoked_asterisk': '✳️', - 'eight_pointed_star': '✴️', - 'sparkle': '❇️', - 'copyright': '©️', - 'registered': '®️', - 'trade_mark': '™️', - 'keycap_': '*️⃣', - 'keycap_0': '0️⃣', - 'keycap_1': '1️⃣', - 'keycap_2': '2️⃣', - 'keycap_3': '3️⃣', - 'keycap_4': '4️⃣', - 'keycap_5': '5️⃣', - 'keycap_6': '6️⃣', - 'keycap_7': '7️⃣', - 'keycap_8': '8️⃣', - 'keycap_9': '9️⃣', - 'keycap_10': '🔟', - 'input_latin_uppercase': '🔠', - 'input_latin_lowercase': '🔡', - 'input_numbers': '🔢', - 'input_symbols': '🔣', - 'input_latin_letters': '🔤', - 'a_button': '🅰️', - 'ab_button': '🆎', - 'b_button': '🅱️', - 'cl_button': '🆑', - 'cool_button': '🆒', - 'free_button': '🆓', - 'information': 'ℹ️', - 'id_button': '🆔', - 'circled_m': 'Ⓜ️', - 'new_button': '🆕', - 'ng_button': '🆖', - 'o_button': '🅾️', - 'ok_button': '🆗', - 'p_button': '🅿️', - 'sos_button': '🆘', - 'up_button': '🆙', - 'vs_button': '🆚', - 'japanese_here_button': '🈁', - 'japanese_service_charge_button': '🈂️', - 'japanese_monthly_amount_button': '🈷️', - 'japanese_not_free_of_charge_button': '🈶', - 'japanese_reserved_button': '🈯', - 'japanese_bargain_button': '🉐', - 'japanese_discount_button': '🈹', - 'japanese_free_of_charge_button': '🈚', - 'japanese_prohibited_button': '🈲', - 'japanese_acceptable_button': '🉑', - 'japanese_application_button': '🈸', - 'japanese_passing_grade_button': '🈴', - 'japanese_vacancy_button': '🈳', - 'japanese_congratulations_button': '㊗️', - 'japanese_secret_button': '㊙️', - 'japanese_open_for_business_button': '🈺', - 'japanese_no_vacancy_button': '🈵', - 'red_circle': '🔴', - 'orange_circle': '🟠', - 'yellow_circle': '🟡', - 'green_circle': '🟢', - 'blue_circle': '🔵', - 'purple_circle': '🟣', - 'brown_circle': '🟤', - 'black_circle': '⚫', - 'white_circle': '⚪', - 'red_square': '🟥', - 'orange_square': '🟧', - 'yellow_square': '🟨', - 'green_square': '🟩', - 'blue_square': '🟦', - 'purple_square': '🟪', - 'brown_square': '🟫', - 'black_large_square': '⬛', - 'white_large_square': '⬜', - 'black_medium_square': '◼️', - 'white_medium_square': '◻️', - 'black_medium_small_square': '◾', - 'white_medium_small_square': '◽', - 'black_small_square': '▪️', - 'white_small_square': '▫️', - 'large_orange_diamond': '🔶', - 'large_blue_diamond': '🔷', - 'small_orange_diamond': '🔸', - 'small_blue_diamond': '🔹', - 'red_triangle_pointed_up': '🔺', - 'red_triangle_pointed_down': '🔻', - 'diamond_with_a_dot': '💠', - 'radio_button': '🔘', - 'white_square_button': '🔳', - 'black_square_button': '🔲', - 'chequered_flag': '🏁', - 'triangular_flag': '🚩', - 'crossed_flags': '🎌', - 'black_flag': '🏴', - 'white_flag': '🏳️', - 'rainbow_flag': '🏳️‍🌈', - 'transgender_flag': '🏳️‍⚧️', - 'pirate_flag': '🏴‍☠️', - 'flag_ascension_island': '🇦🇨', - 'flag_andorra': '🇦🇩', - 'flag_united_arab_emirates': '🇦🇪', - 'flag_afghanistan': '🇦🇫', - 'flag_antigua_barbuda': '🇦🇬', - 'flag_anguilla': '🇦🇮', - 'flag_albania': '🇦🇱', - 'flag_armenia': '🇦🇲', - 'flag_angola': '🇦🇴', - 'flag_antarctica': '🇦🇶', - 'flag_argentina': '🇦🇷', - 'flag_american_samoa': '🇦🇸', - 'flag_austria': '🇦🇹', - 'flag_australia': '🇦🇺', - 'flag_aruba': '🇦🇼', - 'flag_aland_islands': '🇦🇽', - 'flag_azerbaijan': '🇦🇿', - 'flag_bosnia_herzegovina': '🇧🇦', - 'flag_barbados': '🇧🇧', - 'flag_bangladesh': '🇧🇩', - 'flag_belgium': '🇧🇪', - 'flag_burkina_faso': '🇧🇫', - 'flag_bulgaria': '🇧🇬', - 'flag_bahrain': '🇧🇭', - 'flag_burundi': '🇧🇮', - 'flag_benin': '🇧🇯', - 'flag_st_barthelemy': '🇧🇱', - 'flag_bermuda': '🇧🇲', - 'flag_brunei': '🇧🇳', - 'flag_bolivia': '🇧🇴', - 'flag_caribbean_netherlands': '🇧🇶', - 'flag_brazil': '🇧🇷', - 'flag_bahamas': '🇧🇸', - 'flag_bhutan': '🇧🇹', - 'flag_bouvet_island': '🇧🇻', - 'flag_botswana': '🇧🇼', - 'flag_belarus': '🇧🇾', - 'flag_belize': '🇧🇿', - 'flag_canada': '🇨🇦', - 'flag_cocos_islands': '🇨🇨', - 'flag_congo_kinshasa': '🇨🇩', - 'flag_central_african_republic': '🇨🇫', - 'flag_congo_brazzaville': '🇨🇬', - 'flag_switzerland': '🇨🇭', - 'flag_cote_d_ivoire': '🇨🇮', - 'flag_cook_islands': '🇨🇰', - 'flag_chile': '🇨🇱', - 'flag_cameroon': '🇨🇲', - 'flag_china': '🇨🇳', - 'flag_colombia': '🇨🇴', - 'flag_clipperton_island': '🇨🇵', - 'flag_costa_rica': '🇨🇷', - 'flag_cuba': '🇨🇺', - 'flag_cape_verde': '🇨🇻', - 'flag_curacao': '🇨🇼', - 'flag_christmas_island': '🇨🇽', - 'flag_cyprus': '🇨🇾', - 'flag_czechia': '🇨🇿', - 'flag_germany': '🇩🇪', - 'flag_diego_garcia': '🇩🇬', - 'flag_djibouti': '🇩🇯', - 'flag_denmark': '🇩🇰', - 'flag_dominica': '🇩🇲', - 'flag_dominican_republic': '🇩🇴', - 'flag_algeria': '🇩🇿', - 'flag_ceuta_melilla': '🇪🇦', - 'flag_ecuador': '🇪🇨', - 'flag_estonia': '🇪🇪', - 'flag_egypt': '🇪🇬', - 'flag_western_sahara': '🇪🇭', - 'flag_eritrea': '🇪🇷', - 'flag_spain': '🇪🇸', - 'flag_ethiopia': '🇪🇹', - 'flag_european_union': '🇪🇺', - 'flag_finland': '🇫🇮', - 'flag_fiji': '🇫🇯', - 'flag_falkland_islands': '🇫🇰', - 'flag_micronesia': '🇫🇲', - 'flag_faroe_islands': '🇫🇴', - 'flag_france': '🇫🇷', - 'flag_gabon': '🇬🇦', - 'flag_united_kingdom': '🇬🇧', - 'flag_grenada': '🇬🇩', - 'flag_georgia': '🇬🇪', - 'flag_french_guiana': '🇬🇫', - 'flag_guernsey': '🇬🇬', - 'flag_ghana': '🇬🇭', - 'flag_gibraltar': '🇬🇮', - 'flag_greenland': '🇬🇱', - 'flag_gambia': '🇬🇲', - 'flag_guinea': '🇬🇳', - 'flag_guadeloupe': '🇬🇵', - 'flag_equatorial_guinea': '🇬🇶', - 'flag_greece': '🇬🇷', - 'flag_south_georgia_south_sandwich_islands': '🇬🇸', - 'flag_guatemala': '🇬🇹', - 'flag_guam': '🇬🇺', - 'flag_guinea_bissau': '🇬🇼', - 'flag_guyana': '🇬🇾', - 'flag_hong_kong_sar_china': '🇭🇰', - 'flag_heard_mcdonald_islands': '🇭🇲', - 'flag_honduras': '🇭🇳', - 'flag_croatia': '🇭🇷', - 'flag_haiti': '🇭🇹', - 'flag_hungary': '🇭🇺', - 'flag_canary_islands': '🇮🇨', - 'flag_indonesia': '🇮🇩', - 'flag_ireland': '🇮🇪', - 'flag_israel': '🇮🇱', - 'flag_isle_of_man': '🇮🇲', - 'flag_india': '🇮🇳', - 'flag_british_indian_ocean_territory': '🇮🇴', - 'flag_iraq': '🇮🇶', - 'flag_iran': '🇮🇷', - 'flag_iceland': '🇮🇸', - 'flag_italy': '🇮🇹', - 'flag_jersey': '🇯🇪', - 'flag_jamaica': '🇯🇲', - 'flag_jordan': '🇯🇴', - 'flag_japan': '🇯🇵', - 'flag_kenya': '🇰🇪', - 'flag_kyrgyzstan': '🇰🇬', - 'flag_cambodia': '🇰🇭', - 'flag_kiribati': '🇰🇮', - 'flag_comoros': '🇰🇲', - 'flag_st_kitts_nevis': '🇰🇳', - 'flag_north_korea': '🇰🇵', - 'flag_south_korea': '🇰🇷', - 'flag_kuwait': '🇰🇼', - 'flag_cayman_islands': '🇰🇾', - 'flag_kazakhstan': '🇰🇿', - 'flag_laos': '🇱🇦', - 'flag_lebanon': '🇱🇧', - 'flag_st_lucia': '🇱🇨', - 'flag_liechtenstein': '🇱🇮', - 'flag_sri_lanka': '🇱🇰', - 'flag_liberia': '🇱🇷', - 'flag_lesotho': '🇱🇸', - 'flag_lithuania': '🇱🇹', - 'flag_luxembourg': '🇱🇺', - 'flag_latvia': '🇱🇻', - 'flag_libya': '🇱🇾', - 'flag_morocco': '🇲🇦', - 'flag_monaco': '🇲🇨', - 'flag_moldova': '🇲🇩', - 'flag_montenegro': '🇲🇪', - 'flag_st_martin': '🇲🇫', - 'flag_madagascar': '🇲🇬', - 'flag_marshall_islands': '🇲🇭', - 'flag_north_macedonia': '🇲🇰', - 'flag_mali': '🇲🇱', - 'flag_myanmar': '🇲🇲', - 'flag_mongolia': '🇲🇳', - 'flag_macao_sar_china': '🇲🇴', - 'flag_northern_mariana_islands': '🇲🇵', - 'flag_martinique': '🇲🇶', - 'flag_mauritania': '🇲🇷', - 'flag_montserrat': '🇲🇸', - 'flag_malta': '🇲🇹', - 'flag_mauritius': '🇲🇺', - 'flag_maldives': '🇲🇻', - 'flag_malawi': '🇲🇼', - 'flag_mexico': '🇲🇽', - 'flag_malaysia': '🇲🇾', - 'flag_mozambique': '🇲🇿', - 'flag_namibia': '🇳🇦', - 'flag_new_caledonia': '🇳🇨', - 'flag_niger': '🇳🇪', - 'flag_norfolk_island': '🇳🇫', - 'flag_nigeria': '🇳🇬', - 'flag_nicaragua': '🇳🇮', - 'flag_netherlands': '🇳🇱', - 'flag_norway': '🇳🇴', - 'flag_nepal': '🇳🇵', - 'flag_nauru': '🇳🇷', - 'flag_niue': '🇳🇺', - 'flag_new_zealand': '🇳🇿', - 'flag_oman': '🇴🇲', - 'flag_panama': '🇵🇦', - 'flag_peru': '🇵🇪', - 'flag_french_polynesia': '🇵🇫', - 'flag_papua_new_guinea': '🇵🇬', - 'flag_philippines': '🇵🇭', - 'flag_pakistan': '🇵🇰', - 'flag_poland': '🇵🇱', - 'flag_st_pierre_miquelon': '🇵🇲', - 'flag_pitcairn_islands': '🇵🇳', - 'flag_puerto_rico': '🇵🇷', - 'flag_palestinian_territories': '🇵🇸', - 'flag_portugal': '🇵🇹', - 'flag_palau': '🇵🇼', - 'flag_paraguay': '🇵🇾', - 'flag_qatar': '🇶🇦', - 'flag_reunion': '🇷🇪', - 'flag_romania': '🇷🇴', - 'flag_serbia': '🇷🇸', - 'flag_russia': '🇷🇺', - 'flag_rwanda': '🇷🇼', - 'flag_saudi_arabia': '🇸🇦', - 'flag_solomon_islands': '🇸🇧', - 'flag_seychelles': '🇸🇨', - 'flag_sudan': '🇸🇩', - 'flag_sweden': '🇸🇪', - 'flag_singapore': '🇸🇬', - 'flag_st_helena': '🇸🇭', - 'flag_slovenia': '🇸🇮', - 'flag_svalbard_jan_mayen': '🇸🇯', - 'flag_slovakia': '🇸🇰', - 'flag_sierra_leone': '🇸🇱', - 'flag_san_marino': '🇸🇲', - 'flag_senegal': '🇸🇳', - 'flag_somalia': '🇸🇴', - 'flag_suriname': '🇸🇷', - 'flag_south_sudan': '🇸🇸', - 'flag_sao_tome_principe': '🇸🇹', - 'flag_el_salvador': '🇸🇻', - 'flag_sint_maarten': '🇸🇽', - 'flag_syria': '🇸🇾', - 'flag_eswatini': '🇸🇿', - 'flag_tristan_da_cunha': '🇹🇦', - 'flag_turks_caicos_islands': '🇹🇨', - 'flag_chad': '🇹🇩', - 'flag_french_southern_territories': '🇹🇫', - 'flag_togo': '🇹🇬', - 'flag_thailand': '🇹🇭', - 'flag_tajikistan': '🇹🇯', - 'flag_tokelau': '🇹🇰', - 'flag_timor_leste': '🇹🇱', - 'flag_turkmenistan': '🇹🇲', - 'flag_tunisia': '🇹🇳', - 'flag_tonga': '🇹🇴', - 'flag_turkey': '🇹🇷', - 'flag_trinidad_tobago': '🇹🇹', - 'flag_tuvalu': '🇹🇻', - 'flag_taiwan': '🇹🇼', - 'flag_tanzania': '🇹🇿', - 'flag_ukraine': '🇺🇦', - 'flag_uganda': '🇺🇬', - 'flag_u_s_outlying_islands': '🇺🇲', - 'flag_united_nations': '🇺🇳', - 'flag_united_states': '🇺🇸', - 'flag_uruguay': '🇺🇾', - 'flag_uzbekistan': '🇺🇿', - 'flag_vatican_city': '🇻🇦', - 'flag_st_vincent_grenadines': '🇻🇨', - 'flag_venezuela': '🇻🇪', - 'flag_british_virgin_islands': '🇻🇬', - 'flag_u_s_virgin_islands': '🇻🇮', - 'flag_vietnam': '🇻🇳', - 'flag_vanuatu': '🇻🇺', - 'flag_wallis_futuna': '🇼🇫', - 'flag_samoa': '🇼🇸', - 'flag_kosovo': '🇽🇰', - 'flag_yemen': '🇾🇪', - 'flag_mayotte': '🇾🇹', - 'flag_south_africa': '🇿🇦', - 'flag_zambia': '🇿🇲', - 'flag_zimbabwe': '🇿🇼', - 'flag_england': '🏴󠁧󠁢󠁥󠁮󠁧󠁿', - 'flag_scotland': '🏴󠁧󠁢󠁳󠁣󠁴󠁿', - 'flag_wales': '🏴󠁧󠁢󠁷󠁬󠁳󠁿', -}; diff --git a/frontend/app_flowy/lib/workspace/application/markdown/src/extension_set.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/extension_set.dart deleted file mode 100644 index eadda7bc05..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/extension_set.dart +++ /dev/null @@ -1,64 +0,0 @@ -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/frontend/app_flowy/lib/workspace/application/markdown/src/html_renderer.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/html_renderer.dart deleted file mode 100644 index d6630e297c..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/html_renderer.dart +++ /dev/null @@ -1,121 +0,0 @@ -// 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/frontend/app_flowy/lib/workspace/application/markdown/src/inline_parser.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/inline_parser.dart deleted file mode 100644 index ce0f11302e..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/inline_parser.dart +++ /dev/null @@ -1,1270 +0,0 @@ -// 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 overridden 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 deactivate 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 followed 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/frontend/app_flowy/lib/workspace/application/markdown/src/util.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/util.dart deleted file mode 100644 index aed4c3c3fc..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/util.dart +++ /dev/null @@ -1,71 +0,0 @@ -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/frontend/app_flowy/lib/workspace/application/markdown/src/version.dart b/frontend/app_flowy/lib/workspace/application/markdown/src/version.dart deleted file mode 100644 index 19433ffa4a..0000000000 --- a/frontend/app_flowy/lib/workspace/application/markdown/src/version.dart +++ /dev/null @@ -1,2 +0,0 @@ -// Generated code. Do not modify. -const packageVersion = '0.0.2'; diff --git a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart index debb1e71c3..5786998ef8 100644 --- a/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/menu/menu_bloc.dart @@ -6,6 +6,8 @@ import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -13,16 +15,23 @@ part 'menu_bloc.freezed.dart'; class MenuBloc extends Bloc { final WorkspaceService _workspaceService; - final WorkspaceListener listener; - final String workspaceId; + final WorkspaceListener _listener; + final UserProfilePB user; + final WorkspacePB workspace; - MenuBloc({required this.workspaceId, required this.listener}) - : _workspaceService = WorkspaceService(workspaceId: workspaceId), - super(MenuState.initial()) { + MenuBloc({ + required this.user, + required this.workspace, + }) : _workspaceService = WorkspaceService(workspaceId: workspace.id), + _listener = WorkspaceListener( + user: user, + workspaceId: workspace.id, + ), + super(MenuState.initial(workspace)) { on((event, emit) async { await event.map( initial: (e) async { - listener.start(appsChanged: _handleAppsOrFail); + _listener.start(appsChanged: _handleAppsOrFail); await _fetchApps(emit); }, openPage: (e) async { @@ -55,7 +64,7 @@ class MenuBloc extends Bloc { @override Future close() async { - await listener.stop(); + await _listener.stop(); return super.close(); } @@ -110,8 +119,8 @@ class MenuState with _$MenuState { required Plugin plugin, }) = _MenuState; - factory MenuState.initial() => MenuState( - apps: [], + factory MenuState.initial(WorkspacePB workspace) => MenuState( + apps: workspace.apps.items, successOrFailure: left(unit), plugin: makePlugin(pluginType: PluginType.blank), ); diff --git a/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart b/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart index e343b6f9fe..507c727aa8 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_bloc.dart @@ -15,9 +15,9 @@ class ViewBloc extends Bloc { ViewBloc({ required this.view, - required this.service, - required this.listener, - }) : super(ViewState.init(view)) { + }) : service = ViewService(), + listener = ViewListener(view: view), + super(ViewState.init(view)) { on((event, emit) async { await event.map( initial: (e) { @@ -31,14 +31,19 @@ class ViewBloc extends Bloc { }, viewDidUpdate: (e) { e.result.fold( - (view) => - emit(state.copyWith(view: view, successOrFailure: left(unit))), - (error) => emit(state.copyWith(successOrFailure: right(error))), + (view) => emit( + state.copyWith(view: view, successOrFailure: left(unit)), + ), + (error) => emit( + state.copyWith(successOrFailure: right(error)), + ), ); }, rename: (e) async { - final result = - await service.updateView(viewId: view.id, name: e.newName); + final result = await service.updateView( + viewId: view.id, + name: e.newName, + ); emit( result.fold( (l) => state.copyWith(successOrFailure: left(unit)), @@ -56,7 +61,7 @@ class ViewBloc extends Bloc { ); }, duplicate: (e) async { - final result = await service.duplicate(viewId: view.id); + final result = await service.duplicate(view: view); emit( result.fold( (l) => state.copyWith(successOrFailure: left(unit)), diff --git a/frontend/app_flowy/lib/workspace/application/view/view_service.dart b/frontend/app_flowy/lib/workspace/application/view/view_service.dart index b73cf25cad..7cfbbfeeb7 100644 --- a/frontend/app_flowy/lib/workspace/application/view/view_service.dart +++ b/frontend/app_flowy/lib/workspace/application/view/view_service.dart @@ -10,7 +10,8 @@ class ViewService { return FolderEventReadView(request).send(); } - Future> updateView({required String viewId, String? name, String? desc}) { + Future> updateView( + {required String viewId, String? name, String? desc}) { final request = UpdateViewPayloadPB.create()..viewId = viewId; if (name != null) { @@ -29,8 +30,7 @@ class ViewService { return FolderEventDeleteView(request).send(); } - Future> duplicate({required String viewId}) { - final request = ViewIdPB(value: viewId); - return FolderEventDuplicateView(request).send(); + Future> duplicate({required ViewPB view}) { + return FolderEventDuplicateView(view).send(); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart index ae4dc7f3b9..4995289bc0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_screen.dart @@ -25,7 +25,7 @@ import 'menu/menu.dart'; class HomeScreen extends StatefulWidget { final UserProfilePB user; - final CurrentWorkspaceSettingPB workspaceSetting; + final WorkspaceSettingPB workspaceSetting; const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart index 4ae0420b36..ab92ff6d86 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/home_stack.dart @@ -1,7 +1,6 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/plugins/blank/blank.dart'; import 'package:app_flowy/workspace/presentation/home/toast.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -32,14 +31,13 @@ class HomeStack extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Column( mainAxisAlignment: MainAxisAlignment.start, children: [ getIt().stackTopBar(layout: layout), Expanded( child: Container( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, child: FocusTraversalGroup( child: getIt().stackWidget( onDeleted: (view, index) { @@ -198,9 +196,8 @@ class HomeTopBar extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Container( - color: theme.surface, + color: Theme.of(context).colorScheme.surface, height: HomeSizes.topBarHeight, child: Row( crossAxisAlignment: CrossAxisAlignment.center, diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/create_button.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/create_button.dart index bd6ab040e1..fd86c79fd4 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/create_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/create_button.dart @@ -17,7 +17,9 @@ class NewAppButton extends StatelessWidget { Widget build(BuildContext context) { final child = FlowyTextButton( LocaleKeys.newPageText.tr(), - fontSize: 12, + hoverColor: Colors.transparent, + fontSize: FontSizes.s12, + fontWeight: FontWeight.w500, onPressed: () async => await _showCreateAppDialog(context), heading: svgWithSize("home/new_app", const Size(16, 16)), padding: EdgeInsets.symmetric(horizontal: Insets.l, vertical: 20), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart index 80dd484dcb..ab1a8ee86c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/add_button.dart @@ -1,16 +1,14 @@ import 'package:app_flowy/startup/plugin/plugin.dart'; +import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra/image.dart'; -import 'package:flowy_infra/theme.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; class AddButton extends StatelessWidget { final Function(PluginBuilder) onSelected; + const AddButton({ Key? key, required this.onSelected, @@ -18,96 +16,47 @@ class AddButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return FlowyIconButton( - hoverColor: theme.hover, - width: 22, - onPressed: () { - ActionList( - anchorContext: context, - onSelected: onSelected, - ).show(context); - }, - icon: svgWidget("home/add", color: theme.iconColor) - .padding(horizontal: 3, vertical: 3), + final List actions = []; + actions.addAll( + pluginBuilders() + .map((pluginBuilder) => + AddButtonActionWrapper(pluginBuilder: pluginBuilder)) + .toList(), ); - } -} -class ActionList { - final Function(PluginBuilder) onSelected; - final BuildContext anchorContext; - final String _identifier = 'DisclosureButtonActionList'; - - const ActionList({required this.anchorContext, required this.onSelected}); - - void show(BuildContext buildContext) { - final items = pluginBuilders().map( - (pluginBuilder) { - return CreateItem( - pluginBuilder: pluginBuilder, - onSelected: (builder) { - onSelected(builder); - FlowyOverlay.of(buildContext).remove(_identifier); - }, + return PopoverActionList( + direction: PopoverDirection.bottomWithLeftAligned, + actions: actions, + buildChild: (controller) { + return FlowyIconButton( + width: 22, + onPressed: () => controller.show(), + icon: svgWidget( + "home/add", + color: Theme.of(context).colorScheme.onSurface, + ).padding(horizontal: 3, vertical: 3), ); }, - ).toList(); + onSelected: (action, controller) { + if (action is AddButtonActionWrapper) { + onSelected(action.pluginBuilder); + } - ListOverlay.showWithAnchor( - buildContext, - identifier: _identifier, - itemCount: items.length, - itemBuilder: (context, index) => items[index], - anchorContext: anchorContext, - anchorDirection: AnchorDirection.bottomRight, - constraints: BoxConstraints( - minWidth: 120, - maxWidth: 280, - minHeight: items.length * (CreateItem.height), - maxHeight: items.length * (CreateItem.height), - ), + controller.close(); + }, ); } } -class CreateItem extends StatelessWidget { - static const double height = 30; - static const double verticalPadding = 6; - +class AddButtonActionWrapper extends ActionCell { final PluginBuilder pluginBuilder; - final Function(PluginBuilder) onSelected; - const CreateItem({ - Key? key, - required this.pluginBuilder, - required this.onSelected, - }) : super(key: key); + + AddButtonActionWrapper({required this.pluginBuilder}); @override - Widget build(BuildContext context) { - final theme = context.watch(); - final config = HoverStyle(hoverColor: theme.hover); + Widget? icon(Color iconColor) => + svgWidget(pluginBuilder.menuIcon, color: iconColor); - return FlowyHover( - style: config, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => onSelected(pluginBuilder), - child: ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 120, - minHeight: CreateItem.height, - ), - child: Align( - alignment: Alignment.centerLeft, - child: FlowyText.medium( - pluginBuilder.menuName, - color: theme.textColor, - fontSize: 12, - ).padding(horizontal: 10), - ), - ), - ), - ); - } + @override + String get name => pluginBuilder.menuName; } diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart index d887b3417e..53ff979ff8 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/header/header.dart @@ -4,7 +4,7 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:expandable/expandable.dart'; import 'package:flowy_infra/icon_data.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; import 'package:flutter/material.dart'; @@ -13,6 +13,7 @@ import 'package:app_flowy/workspace/application/app/app_bloc.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:flowy_infra/image.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import '../menu_app.dart'; import 'add_button.dart'; @@ -26,23 +27,22 @@ class MenuAppHeader extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.read(); return SizedBox( height: MenuAppSizes.headerHeight, child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - _renderExpandedIcon(context, theme), + _renderExpandedIcon(context), // HSpace(MenuAppSizes.iconPadding), - _renderTitle(context, theme), + _renderTitle(context), _renderCreateViewButton(context), ], ), ); } - Widget _renderExpandedIcon(BuildContext context, AppTheme theme) { + Widget _renderExpandedIcon(BuildContext context) { return SizedBox( width: MenuAppSizes.headerHeight, height: MenuAppSizes.headerHeight, @@ -56,7 +56,7 @@ class MenuAppHeader extends StatelessWidget { theme: ExpandableThemeData( expandIcon: FlowyIconData.drop_down_show, collapseIcon: FlowyIconData.drop_down_hide, - iconColor: theme.shader1, + iconColor: Theme.of(context).colorScheme.onSurface, iconSize: MenuAppSizes.iconSize, iconPadding: const EdgeInsets.fromLTRB(0, 0, 10, 0), hasIcon: false, @@ -66,7 +66,7 @@ class MenuAppHeader extends StatelessWidget { ); } - Widget _renderTitle(BuildContext context, AppTheme theme) { + Widget _renderTitle(BuildContext context) { return Expanded( child: BlocListener( listenWhen: (p, c) => @@ -102,6 +102,7 @@ class MenuAppHeader extends StatelessWidget { Widget _renderCreateViewButton(BuildContext context) { return Tooltip( message: LocaleKeys.menuAppHeader_addPageTooltip.tr(), + textStyle: TextStyles.caption.textColor(Colors.white), child: AddButton( onSelected: (pluginBuilder) { context.read().add( @@ -150,7 +151,6 @@ class AppActionList extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.read(); return PopoverActionList( direction: PopoverDirection.bottomWithCenterAligned, actions: AppDisclosureAction.values @@ -170,7 +170,6 @@ class AppActionList extends StatelessWidget { builder: (context, app) => FlowyText.medium( app.name, fontSize: 12, - color: theme.textColor, overflow: TextOverflow.ellipsis, ), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart index b4a2e3532c..13127e4cea 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/menu_app.dart @@ -1,4 +1,3 @@ -import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:expandable/expandable.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; @@ -87,10 +86,7 @@ class _MenuAppState extends State { iconPadding: EdgeInsets.zero, hasIcon: false, ), - header: ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: MenuAppHeader(widget.app), - ), + header: MenuAppHeader(widget.app), expanded: ViewSection(appViewData: viewDataContext), collapsed: const SizedBox(), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart index 5b24fcd138..3938eeb3f3 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/app/section/item.dart @@ -4,7 +4,7 @@ import 'package:app_flowy/workspace/application/view/view_ext.dart'; import 'package:app_flowy/workspace/presentation/home/menu/menu.dart'; import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -34,14 +34,14 @@ class ViewSectionItem extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return MultiBlocProvider( providers: [ BlocProvider( - create: (ctx) => getIt(param1: view) - ..add( - const ViewEvent.initial(), - )), + create: (ctx) => getIt(param1: view) + ..add( + const ViewEvent.initial(), + ), + ), ], child: BlocBuilder( builder: (blocContext, state) { @@ -50,7 +50,9 @@ class ViewSectionItem extends StatelessWidget { child: InkWell( onTap: () => onSelected(blocContext.read().state.view), child: FlowyHover( - style: HoverStyle(hoverColor: theme.bg3), + style: HoverStyle( + hoverColor: AFThemeExtension.of(context).greySelect, + ), // If current state.isEditing is true, the hover should not // rebuild when onEnter/onExit events happened. buildWhenOnHover: () => !state.isEditing, @@ -58,7 +60,7 @@ class ViewSectionItem extends StatelessWidget { blocContext, onHover, state, - theme.iconColor, + Theme.of(context).colorScheme.onSurface, ), isSelected: () => state.isEditing || isSelected, ), @@ -173,7 +175,6 @@ class ViewDisclosureButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return PopoverActionList( direction: PopoverDirection.bottomWithCenterAligned, actions: ViewDisclosureAction.values @@ -181,9 +182,13 @@ class ViewDisclosureButton extends StatelessWidget { .toList(), buildChild: (controller) { return FlowyIconButton( + hoverColor: Colors.transparent, iconPadding: const EdgeInsets.all(5), width: 26, - icon: svgWidget("editor/details", color: theme.iconColor), + icon: svgWidget( + "editor/details", + color: Theme.of(context).colorScheme.onSurface, + ), onPressed: () { onEdit(true); controller.show(); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart index 1796640d00..664958dbbe 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu.dart @@ -6,9 +6,9 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/plugins/trash/menu.dart'; import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/notifier.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; @@ -26,8 +26,8 @@ import 'package:app_flowy/core/frameless_window.dart'; // import 'package:app_flowy/workspace/presentation/home/home_sizes.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:easy_localization/easy_localization.dart'; +import '../navigation.dart'; import 'app/menu_app.dart'; import 'app/create_button.dart'; import 'menu_user.dart'; @@ -35,7 +35,7 @@ import 'menu_user.dart'; class HomeMenu extends StatelessWidget { final PublishNotifier _collapsedNotifier; final UserProfilePB user; - final CurrentWorkspaceSettingPB workspaceSetting; + final WorkspaceSettingPB workspaceSetting; const HomeMenu({ Key? key, @@ -51,8 +51,10 @@ class HomeMenu extends StatelessWidget { providers: [ BlocProvider( create: (context) { - final menuBloc = getIt( - param1: user, param2: workspaceSetting.workspace.id); + final menuBloc = MenuBloc( + user: user, + workspace: workspaceSetting.workspace, + ); menuBloc.add(const MenuEvent.initial()); return menuBloc; }, @@ -82,9 +84,8 @@ class HomeMenu extends StatelessWidget { Widget _renderBody(BuildContext context) { // nested column: https://siddharthmolleti.com/flutter-box-constraints-nested-column-s-row-s-3dfacada7361 - final theme = context.watch(); return Container( - color: theme.bg1, + color: Theme.of(context).colorScheme.surfaceVariant, child: Column( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -201,43 +202,41 @@ class MenuTopBar extends StatelessWidget { if (Platform.isMacOS) { return Container(); } - final theme = context.watch(); - return (theme.isDark + return (Theme.of(context).brightness == Brightness.dark ? svgWithSize("flowy_logo_dark_mode", const Size(92, 17)) : svgWithSize("flowy_logo_with_text", const Size(92, 17))); } @override Widget build(BuildContext context) { - final theme = context.watch(); return BlocBuilder( builder: (context, state) { return SizedBox( height: HomeSizes.topBarHeight, child: MoveWindowDetector( - child: Row( - children: [ - renderIcon(context), - const Spacer(), - Tooltip( - richMessage: TextSpan(children: [ - TextSpan( - text: "${LocaleKeys.sideBar_closeSidebar.tr()}\n"), - TextSpan( - text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\", - style: const TextStyle(color: Colors.white60), - ), - ]), + child: Row( + children: [ + renderIcon(context), + const Spacer(), + Tooltip( + richMessage: sidebarTooltipTextSpan( + LocaleKeys.sideBar_closeSidebar.tr()), child: FlowyIconButton( width: 28, + hoverColor: Colors.transparent, onPressed: () => context .read() .add(const HomeEvent.collapseMenu()), iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4), - icon: svgWidget("home/hide_menu", color: theme.iconColor), - )) - ], - )), + icon: svgWidget( + "home/hide_menu", + color: Theme.of(context).colorScheme.onSurface, + ), + ), + ) + ], + ), + ), ); }, ); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart index 12a3114f42..37271417e4 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart @@ -4,7 +4,7 @@ import 'package:app_flowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_view.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart' show UserProfilePB; @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class MenuUser extends StatelessWidget { final UserProfilePB user; @@ -64,14 +65,18 @@ class MenuUser extends StatelessWidget { if (name.isEmpty) { name = context.read().state.userProfile.email; } - return FlowyText(name, fontSize: 12, overflow: TextOverflow.ellipsis); + return FlowyText.medium( + name, + fontSize: FontSizes.s12, + overflow: TextOverflow.ellipsis, + ); } Widget _renderSettingsButton(BuildContext context) { - final theme = context.watch(); final userProfile = context.read().state.userProfile; return Tooltip( message: LocaleKeys.settings_menu_open.tr(), + textStyle: TextStyles.caption.textColor(Colors.white), child: IconButton( onPressed: () { showDialog( @@ -83,7 +88,10 @@ class MenuUser extends StatelessWidget { }, icon: SizedBox.square( dimension: 20, - child: svgWidget("home/settings", color: theme.iconColor), + child: svgWidget( + "home/settings", + color: Theme.of(context).colorScheme.onSurface, + ), ), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart index bfdb708013..60c4a7bc73 100644 --- a/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart +++ b/frontend/app_flowy/lib/workspace/presentation/home/navigation.dart @@ -5,7 +5,8 @@ import 'package:app_flowy/workspace/application/home/home_bloc.dart'; import 'package:app_flowy/workspace/presentation/home/home_stack.dart'; import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/notifier.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; @@ -59,8 +60,6 @@ class FlowyNavigation extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return ChangeNotifierProxyProvider( create: (_) { final notifier = Provider.of(context, listen: false); @@ -75,7 +74,7 @@ class FlowyNavigation extends StatelessWidget { Selector>( selector: (context, notifier) => notifier.collapasedNotifier, builder: (ctx, collapsedNotifier, child) => - _renderCollapse(ctx, collapsedNotifier, theme)), + _renderCollapse(ctx, collapsedNotifier)), Selector>( selector: (context, notifier) => notifier.navigationItems, builder: (ctx, items, child) => Expanded( @@ -90,8 +89,8 @@ class FlowyNavigation extends StatelessWidget { ); } - Widget _renderCollapse(BuildContext context, - PublishNotifier collapsedNotifier, AppTheme theme) { + Widget _renderCollapse( + BuildContext context, PublishNotifier collapsedNotifier) { return ChangeNotifierProvider.value( value: collapsedNotifier, child: Consumer( @@ -100,21 +99,20 @@ class FlowyNavigation extends StatelessWidget { return RotationTransition( turns: const AlwaysStoppedAnimation(180 / 360), child: Tooltip( - richMessage: TextSpan(children: [ - TextSpan(text: "${LocaleKeys.sideBar_openSidebar.tr()}\n"), - TextSpan( - text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\", - style: const TextStyle(color: Colors.white60), - ), - ]), + richMessage: sidebarTooltipTextSpan( + LocaleKeys.sideBar_openSidebar.tr()), child: FlowyIconButton( width: 24, + hoverColor: Colors.transparent, onPressed: () { notifier.value = false; ctx.read().add(const HomeEvent.collapseMenu()); }, iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2), - icon: svgWidget("home/hide_menu", color: theme.iconColor), + icon: svgWidget( + "home/hide_menu", + color: Theme.of(context).colorScheme.onSurface, + ), )), ); } else { @@ -198,3 +196,19 @@ class EllipsisNaviItem extends NavigationItem { @override NavigationCallback get action => (id) {}; } + +TextSpan sidebarTooltipTextSpan(String hintText) => TextSpan( + children: [ + TextSpan( + text: "$hintText\n", + style: TextStyles.caption, + ), + TextSpan( + text: Platform.isMacOS ? "⌘+\\" : "Ctrl+\\", + style: TextStyles.general( + fontSize: FontSizes.s11, + color: Colors.white60, + ), + ), + ], + ); diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart index 42a51b7da1..bd987017cc 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,17 +1,16 @@ import 'package:app_flowy/startup/startup.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; -import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_appearance_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_view.dart'; import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:app_flowy/workspace/application/settings/settings_dialog_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:provider/provider.dart'; class SettingsDialog extends StatelessWidget { final UserProfilePB user; @@ -29,51 +28,42 @@ class SettingsDialog extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt(param1: user) - ..add(const SettingsDialogEvent.initial()), - child: BlocBuilder( - builder: (context, state) => ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: FlowyDialog( - title: Text( - LocaleKeys.settings_title.tr(), - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 200, - child: SettingsMenu( - changeSelectedIndex: (index) { - context - .read() - .add(SettingsDialogEvent.setViewIndex(index)); - }, - currentIndex: context - .read() - .state - .viewIndex, - ), - ), - const VerticalDivider(), - const SizedBox(width: 10), - Expanded( - child: getSettingsView( - context - .read() - .state - .viewIndex, - context - .read() - .state - .userProfile), - ) - ], - ), - ), - ))); + create: (context) => getIt(param1: user) + ..add(const SettingsDialogEvent.initial()), + child: BlocBuilder( + builder: (context, state) => FlowyDialog( + title: FlowyText( + LocaleKeys.settings_title.tr(), + fontSize: 20, + fontWeight: FontWeight.w700, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 200, + child: SettingsMenu( + changeSelectedIndex: (index) { + context + .read() + .add(SettingsDialogEvent.setViewIndex(index)); + }, + currentIndex: + context.read().state.viewIndex, + ), + ), + const VerticalDivider(), + const SizedBox(width: 10), + Expanded( + child: getSettingsView( + context.read().state.viewIndex, + context.read().state.userProfile, + ), + ) + ], + ), + ), + ), + ); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart index 54077135b0..a14acd9e2d 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_appearance_view.dart @@ -2,9 +2,11 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; import '../../widgets/toggle/toggle.dart'; @@ -13,8 +15,6 @@ class SettingsAppearanceView extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -23,22 +23,16 @@ class SettingsAppearanceView extends StatelessWidget { children: [ Text( LocaleKeys.settings_appearance_lightLabel.tr(), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), + style: TextStyles.body1.size(FontSizes.s14), ), Toggle( - value: theme.isDark, + value: Theme.of(context).brightness == Brightness.dark, onChanged: (_) => setTheme(context), - style: ToggleStyle.big(theme), + style: ToggleStyle.big, ), Text( LocaleKeys.settings_appearance_darkLabel.tr(), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), + style: TextStyles.body1.size(FontSizes.s14), ), ], ), @@ -48,11 +42,10 @@ class SettingsAppearanceView extends StatelessWidget { } void setTheme(BuildContext context) { - final theme = context.read(); - if (theme.isDark) { - context.read().setTheme(ThemeType.light); + if (Theme.of(context).brightness == Brightness.dark) { + context.read().setTheme(Brightness.light); } else { - context.read().setTheme(ThemeType.dark); + context.read().setTheme(Brightness.dark); } } } diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart index 457d3b2f59..edf229bee0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_language_view.dart @@ -1,37 +1,32 @@ import 'package:app_flowy/generated/locale_keys.g.dart'; import 'package:app_flowy/workspace/application/appearance.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; import 'package:flowy_infra/language.dart'; -import 'package:provider/provider.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class SettingsLanguageView extends StatelessWidget { const SettingsLanguageView({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - context.watch(); - return ChangeNotifierProvider.value( - value: Provider.of(context, listen: true), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - LocaleKeys.settings_menu_language.tr(), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const LanguageSelectorDropdown(), - ], - ), - ], - ), + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + LocaleKeys.settings_menu_language.tr(), + style: TextStyles.body1.size(FontSizes.s14), + ), + const LanguageSelectorDropdown(), + ], + ), + ], ), ); } @@ -49,7 +44,6 @@ class LanguageSelectorDropdown extends StatefulWidget { class _LanguageSelectorDropdownState extends State { Color currHoverColor = Colors.white.withOpacity(0.0); - late Color themedHoverColor; void hoverExitLanguage() { setState(() { currHoverColor = Colors.white.withOpacity(0.0); @@ -58,15 +52,12 @@ class _LanguageSelectorDropdownState extends State { void hoverEnterLanguage() { setState(() { - currHoverColor = themedHoverColor; + currHoverColor = Theme.of(context).colorScheme.primary; }); } @override Widget build(BuildContext context) { - final theme = context.watch(); - themedHoverColor = theme.main2; - return MouseRegion( onEnter: (event) => {hoverEnterLanguage()}, onExit: (event) => {hoverExitLanguage()}, @@ -78,10 +69,12 @@ class _LanguageSelectorDropdownState extends State { ), child: DropdownButtonHideUnderline( child: DropdownButton( - value: context.read().locale, + value: context.locale, onChanged: (val) { setState(() { - context.read().setLocale(context, val!); + context + .read() + .setLocale(context, val!); }); }, icon: const Visibility( @@ -96,11 +89,7 @@ class _LanguageSelectorDropdownState extends State { padding: const EdgeInsets.all(12.0), child: Text( languageFromLocale(locale), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: theme.textColor, - ), + style: TextStyles.body1.size(FontSizes.s14), ), ), ); diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_menu_element.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_menu_element.dart index 80e5c16e42..70b5a6071c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_menu_element.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_menu_element.dart @@ -1,6 +1,6 @@ -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; class SettingsMenuElement extends StatelessWidget { const SettingsMenuElement({ @@ -20,30 +20,29 @@ class SettingsMenuElement extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return ListTile( leading: Icon( icon, size: 16, - color: index == currentIndex ? Colors.black : theme.textColor, + color: index == currentIndex + ? Theme.of(context).colorScheme.onSurface + : Theme.of(context).colorScheme.onSurface, ), onTap: () { changeSelectedIndex(index); }, selected: index == currentIndex, - selectedColor: Colors.black, - selectedTileColor: theme.main2, + selectedColor: Theme.of(context).colorScheme.onSurface, + selectedTileColor: Theme.of(context).colorScheme.primaryContainer, + hoverColor: Theme.of(context).colorScheme.primary, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(5), ), minLeadingWidth: 0, - title: Text( + title: FlowyText.semibold( label, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - overflow: TextOverflow.ellipsis, - ), + fontSize: FontSizes.s14, + overflow: TextOverflow.ellipsis, ), ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart index ceabdd9b7b..a3fba334cd 100644 --- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart +++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart @@ -1,4 +1,5 @@ import 'package:app_flowy/startup/startup.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'package:app_flowy/workspace/application/user/settings_user_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -92,7 +93,7 @@ class _CurrentIcon extends StatelessWidget { context: context, builder: (BuildContext context) { return SimpleDialog( - title: const Text('Select an Icon'), + title: const FlowyText.medium('Select an Icon'), children: [ SizedBox( height: 300, width: 300, child: IconGallery(_setIcon)) diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart index 006194fe97..89e2d6ab06 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/dialogs.dart @@ -1,17 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/text_style.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:app_flowy/startup/tasks/app_widget.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; -import 'package:textstyle_extensions/textstyle_extensions.dart'; export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:app_flowy/generated/locale_keys.g.dart'; @@ -44,20 +41,22 @@ class _CreateTextFieldDialog extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); return StyledDialog( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ...[ - FlowyText.medium(widget.title, color: theme.shader4), + FlowyText.medium(widget.title, + color: Theme.of(context).disabledColor), VSpace(Insets.sm * 1.5), ], FlowyFormTextInput( hintText: LocaleKeys.dialogCreatePageNameHint.tr(), initialValue: widget.value, - textStyle: - const TextStyle(fontSize: 24, fontWeight: FontWeight.w400), + textStyle: TextStyles.general( + fontSize: 24, + fontWeight: FontWeight.w400, + ), autoFocus: true, onChanged: (text) { newValue = text; @@ -110,14 +109,16 @@ class _CreateFlowyAlertDialog extends State { @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), + FlowyText.medium( + widget.title, + color: Theme.of(context).disabledColor, + ), ], if (widget.confirm != null) ...[ const VSpace(20), @@ -157,19 +158,19 @@ class NavigatorOkCancelDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return StyledDialog( maxWidth: maxWidth ?? 500, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (title != null) ...[ - FlowyText.medium(title!.toUpperCase(), color: theme.shader1), + FlowyText.medium(title!.toUpperCase()), VSpace(Insets.sm * 1.5), - Container(color: theme.bg1, height: 1), + Container( + color: Theme.of(context).colorScheme.surfaceVariant, height: 1), VSpace(Insets.m * 1.5), ], - Text(message, style: TextStyles.Body1.textHeight(1.5)), + FlowyText.medium(message, fontSize: FontSizes.s12), SizedBox(height: Insets.l), OkCancelButton( onOkPressed: () { diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart index 28cf268a46..a3c056b806 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart @@ -1,21 +1,17 @@ -import 'package:app_flowy/plugins/doc/presentation/toolbar/toolbar_icon_button.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_quill/flutter_quill.dart'; import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; class FlowyEmojiStyleButton extends StatefulWidget { // final Attribute attribute; final String normalIcon; - final double iconSize; - final QuillController controller; + // TODO: enable insert emoji in appflowy_editor + // final QuillController controller; final String tooltipText; const FlowyEmojiStyleButton({ // required this.attribute, required this.normalIcon, - required this.controller, required this.tooltipText, - this.iconSize = defaultIconSize, Key? key, }) : super(key: key); @@ -24,7 +20,6 @@ class FlowyEmojiStyleButton extends StatefulWidget { } class EmojiStyleButtonState extends State { - bool _isToggled = false; // Style get _selectionStyle => widget.controller.getSelectionStyle(); final GlobalKey emojiButtonKey = GlobalKey(); OverlayEntry? _entry; @@ -42,14 +37,15 @@ class EmojiStyleButtonState extends State { // debugPrint(MediaQuery.of(context).size.width.toString()); // debugPrint(MediaQuery.of(context).size.height.toString()); - return ToolbarIconButton( - key: emojiButtonKey, - onPressed: _toggleAttribute, - width: widget.iconSize * kIconButtonFactor, - isToggled: _isToggled, - iconName: widget.normalIcon, - tooltipText: widget.tooltipText, - ); + // return ToolbarIconButton( + // key: emojiButtonKey, + // onPressed: _toggleAttribute, + // width: widget.iconSize * kIconButtonFactor, + // isToggled: _isToggled, + // iconName: widget.normalIcon, + // tooltipText: widget.tooltipText, + // ); + return Container(); } @override @@ -58,71 +54,34 @@ class EmojiStyleButtonState extends State { super.dispose(); } - // @override - // void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) { - // super.didUpdateWidget(oldWidget); - // if (oldWidget.controller != widget.controller) { - // oldWidget.controller.removeListener(_didChangeEditingValue); - // widget.controller.addListener(_didChangeEditingValue); - // _isToggled = _getIsToggled(_selectionStyle.attributes); + // void _toggleAttribute() { + // if (_entry?.mounted ?? false) { + // _entry?.remove(); + // _entry = null; + // setState(() => _isToggled = false); + // } else { + // RenderBox box = + // emojiButtonKey.currentContext?.findRenderObject() as RenderBox; + // Offset position = box.localToGlobal(Offset.zero); + + // // final window = await getWindowInfo(); + + // _entry = OverlayEntry( + // builder: (BuildContext context) => BuildEmojiPickerView( + // controller: widget.controller, + // offset: position, + // ), + // ); + + // Overlay.of(context)!.insert(_entry!); + // setState(() => _isToggled = true); // } + // } - - // @override - // void dispose() { - // widget.controller.removeListener(_didChangeEditingValue); - // super.dispose(); - // } - - // void _didChangeEditingValue() { - // setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes)); - // } - - // bool _getIsToggled(Map attrs) { - // return _entry.mounted; - // } - - void _toggleAttribute() { - if (_entry?.mounted ?? false) { - _entry?.remove(); - _entry = null; - setState(() => _isToggled = false); - } else { - RenderBox box = - emojiButtonKey.currentContext?.findRenderObject() as RenderBox; - Offset position = box.localToGlobal(Offset.zero); - - // final window = await getWindowInfo(); - - _entry = OverlayEntry( - builder: (BuildContext context) => BuildEmojiPickerView( - controller: widget.controller, - offset: position, - ), - ); - - Overlay.of(context)!.insert(_entry!); - setState(() => _isToggled = true); - } - - //TODO @gaganyadav80: INFO: throws error when using TextField with FlowyOverlay. - - // FlowyOverlay.of(context).insertWithRect( - // widget: BuildEmojiPickerView(controller: widget.controller), - // identifier: 'overlay_emoji_picker', - // anchorPosition: Offset(position.dx + 40, position.dy - 10), - // anchorSize: window.frame.size, - // anchorDirection: AnchorDirection.topLeft, - // style: FlowyOverlayStyle(blur: true), - // ); - } } class BuildEmojiPickerView extends StatefulWidget { - const BuildEmojiPickerView({Key? key, required this.controller, this.offset}) - : super(key: key); - - final QuillController controller; + const BuildEmojiPickerView({Key? key, this.offset}) : super(key: key); final Offset? offset; @override @@ -172,15 +131,15 @@ class _BuildEmojiPickerViewState extends State { } void insertEmoji(Emoji emoji) { - final baseOffset = widget.controller.selection.baseOffset; - final extentOffset = widget.controller.selection.extentOffset; - final replaceLen = extentOffset - baseOffset; - final selection = widget.controller.selection.copyWith( - baseOffset: baseOffset + emoji.emoji.length, - extentOffset: baseOffset + emoji.emoji.length, - ); + // final baseOffset = widget.controller.selection.baseOffset; + // final extentOffset = widget.controller.selection.extentOffset; + // final replaceLen = extentOffset - baseOffset; + // final selection = widget.controller.selection.copyWith( + // baseOffset: baseOffset + emoji.emoji.length, + // extentOffset: baseOffset + emoji.emoji.length, + // ); - widget.controller - .replaceText(baseOffset, replaceLen, emoji.emoji, selection); + // widget.controller + // .replaceText(baseOffset, replaceLen, emoji.emoji, selection); } } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index 1f8f0b6837..91d788368e 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -3,13 +3,12 @@ import 'package:app_flowy/workspace/presentation/home/toast.dart'; import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -34,8 +33,6 @@ class BubbleActionList extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - final List actions = []; actions.addAll( BubbleAction.values.map((action) => BubbleActionWrapper(action)), @@ -51,7 +48,7 @@ class BubbleActionList extends StatelessWidget { tooltip: LocaleKeys.questionBubble_help.tr(), fontSize: 12, fontWeight: FontWeight.w600, - fillColor: theme.selector, + fillColor: Theme.of(context).colorScheme.secondaryContainer, mainAxisAlignment: MainAxisAlignment.center, radius: BorderRadius.circular(10), onPressed: () => controller.show(), @@ -121,15 +118,16 @@ class _DebugToast { class FlowyVersionDescription extends CustomActionCell { @override Widget buildWithContext(BuildContext context) { - final theme = context.watch(); - return FutureBuilder( future: PackageInfo.fromPlatform(), builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.hasError) { - return FlowyText("Error: ${snapshot.error}", - fontSize: 12, color: theme.shader4); + return FlowyText( + "Error: ${snapshot.error}", + fontSize: FontSizes.s12, + color: Theme.of(context).disabledColor, + ); } PackageInfo packageInfo = snapshot.data; @@ -143,12 +141,15 @@ class FlowyVersionDescription extends CustomActionCell { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Divider(height: 1, color: theme.shader6, thickness: 1.0), + Divider( + height: 1, + color: Theme.of(context).dividerColor, + thickness: 1.0), const VSpace(6), FlowyText( "$appName $version.$buildNumber", - fontSize: 12, - color: theme.shader4, + fontSize: FontSizes.s12, + color: Theme.of(context).hintColor, ), ], ).padding( @@ -170,7 +171,7 @@ class BubbleActionWrapper extends ActionCell { BubbleActionWrapper(this.inner); @override - Widget? icon(Color iconColor) => inner.emoji; + Widget? icon(Color iconColor) => FlowyText.regular(inner.emoji, fontSize: 12); @override String get name => inner.name; @@ -188,14 +189,14 @@ extension QuestionBubbleExtension on BubbleAction { } } - Widget get emoji { + String get emoji { switch (this) { case BubbleAction.whatsNews: - return const Text('⭐️', style: TextStyle(fontSize: 12)); + return '⭐️'; case BubbleAction.help: - return const Text('👥', style: TextStyle(fontSize: 12)); + return '👥'; case BubbleAction.debug: - return const Text('🐛', style: TextStyle(fontSize: 12)); + return '🐛'; } } } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/left_bar_item.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/left_bar_item.dart index 0d06ec56ce..d609ae6566 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/left_bar_item.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/left_bar_item.dart @@ -1,15 +1,17 @@ import 'package:app_flowy/workspace/application/view/view_listener.dart'; import 'package:app_flowy/workspace/application/view/view_service.dart'; -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class ViewLeftBarItem extends StatefulWidget { final ViewPB view; - ViewLeftBarItem({required this.view, Key? key}) : super(key: ValueKey(view.hashCode)); + ViewLeftBarItem({required this.view, Key? key}) + : super(key: ValueKey(view.hashCode)); @override State createState() => _ViewLeftBarItemState(); @@ -54,7 +56,6 @@ class _ViewLeftBarItemState extends State { Widget build(BuildContext context) { _controller.text = view.name; - final theme = context.watch(); return IntrinsicWidth( key: ValueKey(_controller.text), child: TextField( @@ -66,12 +67,7 @@ class _ViewLeftBarItemState extends State { border: InputBorder.none, isDense: true, ), - style: TextStyle( - color: theme.textColor, - fontSize: 14, - fontWeight: FontWeight.w500, - overflow: TextOverflow.ellipsis, - ), + style: TextStyles.body1.size(FontSizes.s14), // cursorColor: widget.cursorColor, // obscureText: widget.enableObscure, ), diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart index 9aa4df4455..8e8fe5d260 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/pop_up_action.dart @@ -1,11 +1,9 @@ import 'package:appflowy_popover/appflowy_popover.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; class PopoverActionList extends StatefulWidget { @@ -115,11 +113,9 @@ class ActionCellWidget extends StatelessWidget { @override Widget build(BuildContext context) { final actionCell = action as ActionCell; - final theme = context.watch(); - final icon = actionCell.icon(theme.iconColor); + final icon = actionCell.icon(Theme.of(context).colorScheme.onSurface); return FlowyHover( - style: HoverStyle(hoverColor: theme.hover), child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () => onSelected(action), diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle.dart index 4de1c7d376..76952fc21b 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle.dart @@ -1,9 +1,13 @@ import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dart'; -import 'package:flutter/widgets.dart'; +import 'package:flowy_infra/color_extension.dart'; +import 'package:flutter/material.dart'; class Toggle extends StatelessWidget { final ToggleStyle style; final bool value; + final Color? thumbColor; + final Color? activeBackgroundColor; + final Color? inactiveBackgroundColor; final void Function(bool) onChanged; final EdgeInsets padding; @@ -12,11 +16,17 @@ class Toggle extends StatelessWidget { required this.value, required this.onChanged, required this.style, + this.thumbColor, + this.activeBackgroundColor, + this.inactiveBackgroundColor, this.padding = const EdgeInsets.all(8.0), }) : super(key: key); @override Widget build(BuildContext context) { + final backgroundColor = value + ? activeBackgroundColor ?? Theme.of(context).colorScheme.primary + : activeBackgroundColor ?? AFThemeExtension.of(context).toggleOffFill; return GestureDetector( onTap: (() => onChanged(value)), child: Padding( @@ -27,7 +37,7 @@ class Toggle extends StatelessWidget { height: style.height, width: style.width, decoration: BoxDecoration( - color: value ? style.activeBackgroundColor : style.inactiveBackgroundColor, + color: backgroundColor, borderRadius: BorderRadius.circular(style.height / 2), ), ), @@ -39,7 +49,7 @@ class Toggle extends StatelessWidget { height: style.thumbRadius, width: style.thumbRadius, decoration: BoxDecoration( - color: style.thumbColor, + color: thumbColor ?? Theme.of(context).colorScheme.onPrimary, borderRadius: BorderRadius.circular(style.thumbRadius / 2), ), ), diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle_style.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle_style.dart index 683abfab5f..62664d83c0 100644 --- a/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle_style.dart +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/toggle/toggle_style.dart @@ -1,38 +1,18 @@ -import 'package:flowy_infra/theme.dart'; -import 'package:flutter/painting.dart'; -import 'package:flutter/widgets.dart'; - class ToggleStyle { final double height; final double width; final double thumbRadius; - final Color thumbColor; - final Color activeBackgroundColor; - final Color inactiveBackgroundColor; ToggleStyle({ required this.height, required this.width, required this.thumbRadius, - required this.thumbColor, - required this.activeBackgroundColor, - required this.inactiveBackgroundColor, }); - ToggleStyle.big(AppTheme theme) - : height = 16, - width = 27, - thumbRadius = 14, - activeBackgroundColor = theme.main1, - inactiveBackgroundColor = theme.shader5, - thumbColor = theme.surface; + static ToggleStyle get big => + ToggleStyle(height: 16, width: 27, thumbRadius: 14); - ToggleStyle.small(AppTheme theme) - : height = 10, - width = 16, - thumbRadius = 8, - activeBackgroundColor = theme.main1, - inactiveBackgroundColor = theme.shader5, - thumbColor = theme.surface; + static ToggleStyle get small => + ToggleStyle(height: 10, width: 16, thumbRadius: 8); } diff --git a/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md index db9b213e67..d07a7b12fc 100644 --- a/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_editor/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.0.7 +* Refactor theme customizer, and support dark mode. +* Support export and import markdown. +* Refactor example project. +* Fix some bugs. + ## 0.0.6 * Add three plugins: Code Block, LateX, and Horizontal rule. * Support web platform. @@ -29,4 +35,4 @@ Minor Updates to Documentation. ## 0.0.1 -Initial Version of the library. \ No newline at end of file +Initial Version of the library. diff --git a/frontend/app_flowy/packages/appflowy_editor/README.md b/frontend/app_flowy/packages/appflowy_editor/README.md index f6fac062b0..a6f47172de 100644 --- a/frontend/app_flowy/packages/appflowy_editor/README.md +++ b/frontend/app_flowy/packages/appflowy_editor/README.md @@ -38,7 +38,7 @@ and the Flutter guide for * shortcut events * themes * menu options (**coming soon!**) -* [Test-coverage](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and on-going maintenance by AppFlowy's core team and community of more than 1,000 builders +* [Test-coverage](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/testing.md) and ongoing maintenance by AppFlowy's core team and community of more than 1,000 builders ## Getting Started @@ -54,11 +54,9 @@ flutter pub get Start by creating a new empty AppFlowyEditor object. ```dart -final editorStyle = EditorStyle.defaultStyle(); final editorState = EditorState.empty(); // an empty state final editor = AppFlowyEditor( editorState: editorState, - editorStyle: editorStyle, ); ``` @@ -66,11 +64,9 @@ You can also create an editor from a JSON object in order to configure your init ```dart final json = ...; -final editorStyle = EditorStyle.defaultStyle(); final editorState = EditorState(Document.fromJson(data)); final editor = AppFlowyEditor( editorState: editorState, - editorStyle: editorStyle, ); ``` @@ -117,7 +113,7 @@ Below are some examples of shortcut event customizations: Please refer to the API documentation. ## Contributing -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. +Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/contributing-to-appflowy) for details. diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md index 0cd1a231df..2733a79472 100644 --- a/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md +++ b/frontend/app_flowy/packages/appflowy_editor/documentation/customizing.md @@ -16,7 +16,6 @@ Widget build(BuildContext context) { alignment: Alignment.topCenter, child: AppFlowyEditor( editorState: EditorState.empty(), - editorStyle: EditorStyle.defaultStyle(), shortcutEvents: const [], customBuilders: const {}, ), @@ -93,7 +92,7 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) { // Delete the previous 'underscore', // update the style of the text surrounded by the two underscores to 'italic', // and update the cursor position. - TransactionBuilder(editorState) + final transaction = editorState.transaction ..deleteText(textNode, firstUnderscore, 1) ..formatText( textNode, @@ -108,8 +107,8 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) { path: textNode.path, offset: selection.end.offset - 1, ), - ) - ..commit(); + ); + editorState.apply(transaction); return KeyEventResult.handled; }; @@ -125,7 +124,6 @@ Widget build(BuildContext context) { alignment: Alignment.topCenter, child: AppFlowyEditor( editorState: EditorState.empty(), - editorStyle: EditorStyle.defaultStyle(), customBuilders: const {}, shortcutEvents: [ underscoreToItalic, @@ -138,7 +136,7 @@ Widget build(BuildContext context) { ![After](./images/customize_a_shortcut_event_after.gif) -Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart) file of this example. +Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart) file of this example. ## Customizing a Component @@ -156,7 +154,6 @@ Widget build(BuildContext context) { alignment: Alignment.topCenter, child: AppFlowyEditor( editorState: EditorState.empty(), - editorStyle: EditorStyle.defaultStyle(), shortcutEvents: const [], customBuilders: const {}, ), @@ -180,7 +177,7 @@ We'll use `network_image` in this case. And we add `network_image_src` to the `a Then, we create a class that inherits [NodeWidgetBuilder](../lib/src/service/render_plugin_service.dart). As shown in the autoprompt, we need to implement two functions: 1. one returns a widget -2. the other verifies the correctness of the [Node](../lib/src/document/node.dart). +2. the other verifies the correctness of the [Node](../lib/src/core/document/node.dart). ```dart @@ -296,7 +293,6 @@ final editorState = EditorState( ); return AppFlowyEditor( editorState: editorState, - editorStyle: EditorStyle.defaultStyle(), shortcutEvents: const [], customBuilders: { 'network_image': NetworkImageNodeWidgetBuilder(), @@ -308,7 +304,7 @@ return AppFlowyEditor( Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example. -## Customizing a Theme (New Feature in 0.0.5, Alpha) +## Customizing a Theme (New Feature in 0.0.7) We will use a simple example to illustrate how to quickly customize a theme. @@ -322,7 +318,6 @@ Widget build(BuildContext context) { alignment: Alignment.topCenter, child: AppFlowyEditor( editorState: EditorState.empty(), - editorStyle: EditorStyle.defaultStyle(), shortcutEvents: const [], customBuilders: const {}, ), @@ -338,44 +333,41 @@ At this point, the editor looks like ... Next, we will customize the `EditorStyle`. ```dart -EditorStyle _customizedStyle() { - final editorStyle = EditorStyle.defaultStyle(); - return editorStyle.copyWith( - cursorColor: Colors.white, - selectionColor: Colors.blue.withOpacity(0.3), - textStyle: editorStyle.textStyle.copyWith( - defaultTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white, - fontSize: 14.0, - ), - defaultPlaceholderTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white.withOpacity(0.5), - fontSize: 14.0, - ), - bold: const TextStyle(fontWeight: FontWeight.w900), - code: TextStyle( - fontStyle: FontStyle.italic, - color: Colors.red[300], - backgroundColor: Colors.grey.withOpacity(0.3), - ), - highlightColorHex: '0x6FFFEB3B', +ThemeData customizeEditorTheme(BuildContext context) { + final dark = EditorStyle.dark; + final editorStyle = dark.copyWith( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 150), + cursorColor: Colors.red.shade600, + selectionColor: Colors.yellow.shade600.withOpacity(0.5), + textStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.white, ), - pluginStyles: { - 'text/quote': builtInPluginStyle - ..update( - 'textStyle', - (_) { - return (EditorState editorState, Node node) { - return TextStyle( - color: Colors.blue[200], - fontStyle: FontStyle.italic, - fontSize: 12.0, - ); - }; - }, - ), - }, + placeholderTextStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey.shade400, + ), + code: dark.code?.copyWith( + backgroundColor: Colors.lightBlue.shade200, + fontStyle: FontStyle.italic, + ), + highlightColorHex: '0x60FF0000', // red ); + + final quote = QuotedTextPluginStyle.dark.copyWith( + textStyle: (_, __) => GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.blue.shade400, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w700, + ), + ); + + return Theme.of(context).copyWith(extensions: [ + editorStyle, + ...darkPlguinStyleExtension, + quote, + ]); } ``` @@ -389,7 +381,7 @@ Widget build(BuildContext context) { alignment: Alignment.topCenter, child: AppFlowyEditor( editorState: EditorState.empty(), - editorStyle: _customizedStyle(), + themeData: customizeEditorTheme(context), shortcutEvents: const [], customBuilders: const {}, ), diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_after.png b/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_after.png index b45d1cad4e..3890829222 100644 Binary files a/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_after.png and b/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_after.png differ diff --git a/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_before.png b/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_before.png index 2f8a951ee9..8eaac244a5 100644 Binary files a/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_before.png and b/frontend/app_flowy/packages/appflowy_editor/documentation/images/customizing_a_theme_before.png differ diff --git a/frontend/app_flowy/packages/appflowy_editor/example/assets/big_document.json b/frontend/app_flowy/packages/appflowy_editor/example/assets/big_document.json deleted file mode 100644 index 80647bf97e..0000000000 --- a/frontend/app_flowy/packages/appflowy_editor/example/assets/big_document.json +++ /dev/null @@ -1,41960 +0,0 @@ -{ - "document": { - "type": "editor", - "attributes": {}, - "children": [ - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - }, - { - "type": "text", - "delta": [ - { - "insert": "At AppFlowy, we embody what we value deep in our hearts, taking inspiration from other great companies while forging our own path. AppFlowy’s five core values are Mission Driven, Aim High & Iterate, Transparency, Collaboration, and Honesty. Together, they spell MATCH. We will continue to iterate and refine these values as we grow." - } - ] - } - ] - } -} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json b/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json index 2d441d3367..74c82e3227 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json +++ b/frontend/app_flowy/packages/appflowy_editor/example/assets/example.json @@ -2,19 +2,16 @@ "document": { "type": "editor", "children": [ - { - "type": "image", - "attributes": { - "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg", - "align": "center" - } - }, { "type": "text", - "attributes": { "subtype": "heading", "heading": "h1" }, + "attributes": { + "subtype": "heading", + "heading": "h2" + }, "delta": [ { "insert": "👋 " }, - { "insert": "Welcome to ", "attributes": { "bold": true } }, + { "insert": "Welcome to", "attributes": { "bold": true } }, + { "insert": " " }, { "insert": "AppFlowy Editor", "attributes": { @@ -29,7 +26,8 @@ { "type": "text", "delta": [ - { "insert": "AppFlowy Editor is a " }, + { "insert": "AppFlowy Editor is a" }, + { "insert": " " }, { "insert": "highly customizable", "attributes": { "bold": true } }, { "insert": " " }, { "insert": "rich-text editor", "attributes": { "italic": true } }, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/assets/images/icon.png b/frontend/app_flowy/packages/appflowy_editor/example/assets/images/icon.png new file mode 100644 index 0000000000..95e504908b Binary files /dev/null and b/frontend/app_flowy/packages/appflowy_editor/example/assets/images/icon.png differ diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart new file mode 100644 index 0000000000..18d74655ed --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/home_page.dart @@ -0,0 +1,328 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:example/pages/simple_editor.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:universal_html/html.dart' as html; + +enum ExportFileType { + json, + markdown, + html, +} + +extension on ExportFileType { + String get extension { + switch (this) { + case ExportFileType.json: + return 'json'; + case ExportFileType.markdown: + return 'md'; + case ExportFileType.html: + return 'html'; + } + } +} + +class HomePage extends StatefulWidget { + const HomePage({Key? key}) : super(key: key); + + @override + State createState() => _HomePageState(); +} + +class _HomePageState extends State { + final _scaffoldKey = GlobalKey(); + late WidgetBuilder _widgetBuilder; + late EditorState _editorState; + late Future _jsonString; + ThemeData _themeData = ThemeData.light().copyWith( + extensions: [ + ...lightEditorStyleExtension, + ...lightPlguinStyleExtension, + ], + ); + + @override + void initState() { + super.initState(); + + _jsonString = rootBundle.loadString('assets/example.json'); + _widgetBuilder = (context) => SimpleEditor( + jsonString: _jsonString, + themeData: _themeData, + onEditorStateChange: (editorState) { + _editorState = editorState; + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + extendBodyBehindAppBar: true, + drawer: _buildDrawer(context), + body: _buildBody(context), + floatingActionButton: _buildFloatingActionButton(context), + ); + } + + Widget _buildDrawer(BuildContext context) { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + padding: EdgeInsets.zero, + margin: EdgeInsets.zero, + child: Image.asset( + 'assets/images/icon.png', + fit: BoxFit.fill, + ), + ), + + // AppFlowy Editor Demo + _buildSeparator(context, 'AppFlowy Editor Demo'), + _buildListTile(context, 'With Example.json', () { + final jsonString = rootBundle.loadString('assets/example.json'); + _loadEditor(context, jsonString); + }), + _buildListTile(context, 'With Empty Document', () { + final jsonString = Future.value( + jsonEncode(EditorState.empty().document.toJson()).toString(), + ); + _loadEditor(context, jsonString); + }), + + // Encoder Demo + _buildSeparator(context, 'Encoder Demo'), + _buildListTile(context, 'Export To JSON', () { + _exportFile(_editorState, ExportFileType.json); + }), + _buildListTile(context, 'Export to Markdown', () { + _exportFile(_editorState, ExportFileType.markdown); + }), + + // Decoder Demo + _buildSeparator(context, 'Decoder Demo'), + _buildListTile(context, 'Import From JSON', () { + _importFile(ExportFileType.json); + }), + _buildListTile(context, 'Import From Markdown', () { + _importFile(ExportFileType.markdown); + }), + + // Theme Demo + _buildSeparator(context, 'Theme Demo'), + _buildListTile(context, 'Bulit In Dark Mode', () { + _jsonString = Future.value( + jsonEncode(_editorState.document.toJson()).toString(), + ); + setState(() { + _themeData = ThemeData.dark().copyWith( + extensions: [ + ...darkEditorStyleExtension, + ...darkPlguinStyleExtension, + ], + ); + }); + }), + _buildListTile(context, 'Custom Theme', () { + _jsonString = Future.value( + jsonEncode(_editorState.document.toJson()).toString(), + ); + setState(() { + _themeData = _customizeEditorTheme(context); + }); + }), + ], + ), + ); + } + + Widget _buildBody(BuildContext context) { + return _widgetBuilder(context); + } + + Widget _buildListTile( + BuildContext context, + String text, + VoidCallback? onTap, + ) { + return ListTile( + dense: true, + contentPadding: const EdgeInsets.only(left: 16), + title: Text( + text, + style: const TextStyle( + color: Colors.blue, + fontSize: 14, + ), + ), + onTap: () { + Navigator.pop(context); + onTap?.call(); + }, + ); + } + + Widget _buildSeparator(BuildContext context, String text) { + return Padding( + padding: const EdgeInsets.only(left: 16, top: 16, bottom: 4), + child: Text( + text, + style: const TextStyle( + color: Colors.grey, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ); + } + + Widget _buildFloatingActionButton(BuildContext context) { + return FloatingActionButton( + onPressed: () { + _scaffoldKey.currentState?.openDrawer(); + }, + child: const Icon(Icons.menu), + ); + } + + void _loadEditor(BuildContext context, Future jsonString) { + _jsonString = jsonString; + setState( + () { + _widgetBuilder = (context) => SimpleEditor( + jsonString: _jsonString, + themeData: _themeData, + onEditorStateChange: (editorState) { + _editorState = editorState; + }, + ); + }, + ); + } + + void _exportFile( + EditorState editorState, + ExportFileType fileType, + ) async { + var result = ''; + + switch (fileType) { + case ExportFileType.json: + result = jsonEncode(editorState.document.toJson()); + break; + case ExportFileType.markdown: + result = documentToMarkdown(editorState.document); + break; + case ExportFileType.html: + throw UnimplementedError(); + } + + if (!kIsWeb) { + final path = await FilePicker.platform.saveFile( + fileName: 'document.${fileType.extension}', + ); + if (path != null) { + await File(path).writeAsString(result); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('This document is saved to the $path'), + ), + ); + } + } + } else { + final blob = html.Blob([result], 'text/plain', 'native'); + html.AnchorElement( + href: html.Url.createObjectUrlFromBlob(blob).toString(), + ) + ..setAttribute('download', 'document.${fileType.extension}') + ..click(); + } + } + + void _importFile(ExportFileType fileType) async { + final result = await FilePicker.platform.pickFiles( + allowMultiple: false, + allowedExtensions: [fileType.extension], + type: FileType.custom, + ); + var plainText = ''; + if (!kIsWeb) { + final path = result?.files.single.path; + if (path == null) { + return; + } + plainText = await File(path).readAsString(); + } else { + final bytes = result?.files.first.bytes; + if (bytes == null) { + return; + } + plainText = const Utf8Decoder().convert(bytes); + } + + var jsonString = ''; + switch (fileType) { + case ExportFileType.json: + jsonString = jsonEncode(plainText); + break; + case ExportFileType.markdown: + jsonString = jsonEncode(markdownToDocument(plainText).toJson()); + break; + case ExportFileType.html: + throw UnimplementedError(); + } + + if (mounted) { + _loadEditor(context, Future.value(jsonString)); + } + } + + ThemeData _customizeEditorTheme(BuildContext context) { + final dark = EditorStyle.dark; + final editorStyle = dark.copyWith( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 150), + cursorColor: Colors.blue.shade600, + selectionColor: Colors.yellow.shade600.withOpacity(0.5), + textStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey, + ), + placeholderTextStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey.shade500, + ), + code: dark.code?.copyWith( + backgroundColor: Colors.lightBlue.shade200, + fontStyle: FontStyle.italic, + ), + highlightColorHex: '0x60FF0000', // red + ); + + final quote = QuotedTextPluginStyle.dark.copyWith( + textStyle: (_, __) => GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.blue.shade400, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w700, + ), + ); + + return Theme.of(context).copyWith(extensions: [ + editorStyle, + ...darkPlguinStyleExtension, + quote, + ]); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart index 588804e9ac..b98cec364a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/main.dart @@ -1,24 +1,10 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:example/plugin/code_block_node_widget.dart'; -import 'package:example/plugin/horizontal_rule_node_widget.dart'; -import 'package:example/plugin/tex_block_node_widget.dart'; -import 'package:flutter/foundation.dart'; +import 'package:example/home_page.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:example/plugin/underscore_to_italic.dart'; -import 'package:file_picker/file_picker.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'expandable_floating_action_button.dart'; - void main() { runApp(const MyApp()); } @@ -28,19 +14,16 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - localizationsDelegates: const [ + return const MaterialApp( + localizationsDelegates: [ GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, GlobalWidgetsLocalizations.delegate, AppFlowyEditorLocalizations.delegate, ], - supportedLocales: const [Locale('en', 'US')], + supportedLocales: [Locale('en', 'US')], debugShowCheckedModeBanner: false, - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'AppFlowyEditor Example'), + home: MyHomePage(title: 'AppFlowyEditor Example'), ); } } @@ -54,248 +37,8 @@ class MyHomePage extends StatefulWidget { } class _MyHomePageState extends State { - int _pageIndex = 0; - EditorState? _editorState; - bool darkMode = false; - EditorStyle _editorStyle = EditorStyle.defaultStyle(); - Future? _jsonString; - @override Widget build(BuildContext context) { - return Scaffold( - extendBodyBehindAppBar: true, - body: _buildEditor(context), - // body: Center( - // child: ContextMenu(editorState: EditorState.empty(), items: [ - // [ - // ContextMenuItem(name: 'ABCDEFGHIJKLM', onPressed: (editorState) {}), - // ContextMenuItem(name: 'A', onPressed: (editorState) {}), - // ContextMenuItem(name: 'A', onPressed: (editorState) {}) - // ], - // [ - // ContextMenuItem(name: 'B', onPressed: (editorState) {}), - // ContextMenuItem(name: 'B', onPressed: (editorState) {}), - // ContextMenuItem(name: 'B', onPressed: (editorState) {}) - // ] - // ]), - // ), - floatingActionButton: _buildExpandableFab(), - ); - } - - Widget _buildEditor(BuildContext context) { - if (_jsonString != null) { - return _buildEditorWithJsonString(_jsonString!); - } - if (_pageIndex == 0) { - return _buildEditorWithJsonString( - rootBundle.loadString('assets/example.json'), - ); - } else if (_pageIndex == 1) { - return _buildEditorWithJsonString( - rootBundle.loadString('assets/big_document.json'), - ); - } else if (_pageIndex == 2) { - return _buildEditorWithJsonString( - Future.value( - jsonEncode(EditorState.empty().document.toJson()), - ), - ); - } - throw UnimplementedError(); - } - - Widget _buildEditorWithJsonString(Future jsonString) { - return FutureBuilder( - future: jsonString, - builder: (_, snapshot) { - if (snapshot.hasData && - snapshot.connectionState == ConnectionState.done) { - _editorState ??= EditorState( - document: Document.fromJson( - Map.from( - json.decode(snapshot.data!), - ), - ), - ); - _editorState!.logConfiguration - ..level = LogLevel.all - ..handler = (message) { - debugPrint(message); - }; - _editorState!.transactionStream.listen((event) { - debugPrint('Transaction: ${event.toJson()}'); - }); - return Container( - color: darkMode ? Colors.black : Colors.white, - width: MediaQuery.of(context).size.width, - child: AppFlowyEditor( - editorState: _editorState!, - editorStyle: _editorStyle, - editable: true, - customBuilders: { - 'text/code_block': CodeBlockNodeWidgetBuilder(), - 'tex': TeXBlockNodeWidgetBuidler(), - 'horizontal_rule': HorizontalRuleWidgetBuilder(), - }, - shortcutEvents: [ - enterInCodeBlock, - ignoreKeysInCodeBlock, - underscoreToItalic, - insertHorizontalRule, - ], - selectionMenuItems: [ - codeBlockMenuItem, - teXBlockMenuItem, - horizontalRuleMenuItem, - ], - ), - ); - } else { - return const Center( - child: CircularProgressIndicator(), - ); - } - }, - ); - } - - Widget _buildExpandableFab() { - return ExpandableFab( - distance: 112.0, - children: [ - ActionButton( - icon: const Icon(Icons.abc), - onPressed: () => _switchToPage(0), - ), - ActionButton( - icon: const Icon(Icons.abc), - onPressed: () => _switchToPage(1), - ), - ActionButton( - icon: const Icon(Icons.abc), - onPressed: () => _switchToPage(2), - ), - ActionButton( - icon: const Icon(Icons.print), - onPressed: () => _exportDocument(_editorState!), - ), - ActionButton( - icon: const Icon(Icons.import_export), - onPressed: () async => await _importDocument(), - ), - ActionButton( - icon: const Icon(Icons.color_lens), - onPressed: () { - setState(() { - _editorStyle = - darkMode ? EditorStyle.defaultStyle() : _customizedStyle(); - darkMode = !darkMode; - }); - }, - ), - ], - ); - } - - void _exportDocument(EditorState editorState) async { - final document = editorState.document.toJson(); - final json = jsonEncode(document); - if (kIsWeb) { - final blob = html.Blob([json], 'text/plain', 'native'); - html.AnchorElement( - href: html.Url.createObjectUrlFromBlob(blob).toString(), - ) - ..setAttribute('download', 'editor.json') - ..click(); - } else { - final directory = await getTemporaryDirectory(); - final path = directory.path; - final file = File('$path/editor.json'); - await file.writeAsString(json); - - if (mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('The document is saved to the ${file.path}'), - ), - ); - } - } - } - - Future _importDocument() async { - if (kIsWeb) { - final result = await FilePicker.platform.pickFiles( - allowMultiple: false, - allowedExtensions: ['json'], - type: FileType.custom, - ); - final bytes = result?.files.first.bytes; - if (bytes != null) { - final jsonString = const Utf8Decoder().convert(bytes); - setState(() { - _editorState = null; - _jsonString = Future.value(jsonString); - }); - } - } else { - final directory = await getTemporaryDirectory(); - final path = '${directory.path}/editor.json'; - final file = File(path); - setState(() { - _editorState = null; - _jsonString = file.readAsString(); - }); - } - } - - void _switchToPage(int pageIndex) { - if (pageIndex != _pageIndex) { - setState(() { - _editorState = null; - _pageIndex = pageIndex; - }); - } - } - - EditorStyle _customizedStyle() { - final editorStyle = EditorStyle.defaultStyle(); - return editorStyle.copyWith( - cursorColor: Colors.white, - selectionColor: Colors.blue.withOpacity(0.3), - textStyle: editorStyle.textStyle.copyWith( - defaultTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white, - fontSize: 14.0, - ), - defaultPlaceholderTextStyle: GoogleFonts.poppins().copyWith( - color: Colors.white.withOpacity(0.5), - fontSize: 14.0, - ), - bold: const TextStyle(fontWeight: FontWeight.w900), - code: TextStyle( - fontStyle: FontStyle.italic, - color: Colors.red[300], - backgroundColor: Colors.grey.withOpacity(0.3), - ), - highlightColorHex: '0x6FFFEB3B', - ), - pluginStyles: { - 'text/quote': builtInPluginStyle - ..update( - 'textStyle', - (_) { - return (EditorState editorState, Node node) { - return TextStyle( - color: Colors.blue[200], - fontStyle: FontStyle.italic, - fontSize: 12.0, - ); - }; - }, - ), - }, - ); + return const HomePage(); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart new file mode 100644 index 0000000000..5fa772d11f --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/pages/simple_editor.dart @@ -0,0 +1,46 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +class SimpleEditor extends StatelessWidget { + const SimpleEditor({ + super.key, + required this.jsonString, + required this.themeData, + required this.onEditorStateChange, + }); + + final Future jsonString; + final ThemeData themeData; + final void Function(EditorState editorState) onEditorStateChange; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: jsonString, + builder: (context, snapshot) { + if (snapshot.hasData && + snapshot.connectionState == ConnectionState.done) { + final editorState = EditorState( + document: Document.fromJson( + Map.from( + json.decode(snapshot.data!), + ), + ), + ); + onEditorStateChange(editorState); + return AppFlowyEditor( + editorState: editorState, + themeData: themeData, + autoFocus: editorState.document.isEmpty, + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }, + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart index a82d20157e..5ecf4d4ed8 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/code_block_node_widget.dart @@ -46,7 +46,7 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) { SelectionMenuItem codeBlockMenuItem = SelectionMenuItem( name: () => 'Code Block', - icon: const Icon( + icon: (_, __) => const Icon( Icons.abc, color: Colors.black, size: 18.0, @@ -167,7 +167,7 @@ class __CodeBlockNodeWidgeState extends State<_CodeBlockNodeWidge> textNode: widget.textNode, editorState: widget.editorState, textSpanDecorator: (textSpan) => TextSpan( - style: widget.editorState.editorStyle.textStyle.defaultTextStyle, + style: widget.editorState.editorStyle.textStyle, children: codeTextSpan, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/editor_theme.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/editor_theme.dart new file mode 100644 index 0000000000..65a8f868fa --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/editor_theme.dart @@ -0,0 +1,40 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +ThemeData customizeEditorTheme(BuildContext context) { + final dark = EditorStyle.dark; + final editorStyle = dark.copyWith( + padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 150), + cursorColor: Colors.red.shade600, + selectionColor: Colors.yellow.shade600.withOpacity(0.5), + textStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.white, + ), + placeholderTextStyle: GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.grey.shade400, + ), + code: dark.code?.copyWith( + backgroundColor: Colors.lightBlue.shade200, + fontStyle: FontStyle.italic, + ), + highlightColorHex: '0x60FF0000', // red + ); + + final quote = QuotedTextPluginStyle.dark.copyWith( + textStyle: (_, __) => GoogleFonts.poppins().copyWith( + fontSize: 14, + color: Colors.blue.shade400, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w700, + ), + ); + + return Theme.of(context).copyWith(extensions: [ + editorStyle, + ...darkPlguinStyleExtension, + quote, + ]); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart index c38cc0846c..0ca302de18 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/horizontal_rule_node_widget.dart @@ -38,7 +38,7 @@ ShortcutEventHandler _insertHorzaontalRule = (editorState, event) { SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem( name: () => 'Horizontal rule', - icon: const Icon( + icon: (_, __) => const Icon( Icons.horizontal_rule, color: Colors.black, size: 18.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart index a6b958b0ed..c9b0e8d478 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/tex_block_node_widget.dart @@ -6,7 +6,7 @@ import 'package:flutter_math_fork/flutter_math.dart'; SelectionMenuItem teXBlockMenuItem = SelectionMenuItem( name: () => 'Tex', - icon: const Icon( + icon: (_, __) => const Icon( Icons.text_fields_rounded, color: Colors.black, size: 18.0, diff --git a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart b/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart deleted file mode 100644 index 5efbee91d2..0000000000 --- a/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter/material.dart'; - -ShortcutEvent underscoreToItalic = ShortcutEvent( - key: 'Underscore to italic', - command: 'shift+underscore', - handler: _underscoreToItalicHandler, -); - -ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) { - // Obtain the selection and selected nodes of the current document through the 'selectionService' - // to determine whether the selection is collapsed and whether the selected node is a text node. - final selectionService = editorState.service.selectionService; - final selection = selectionService.currentSelection.value; - final textNodes = selectionService.currentSelectedNodes.whereType(); - if (selection == null || !selection.isSingle || textNodes.length != 1) { - return KeyEventResult.ignored; - } - - final textNode = textNodes.first; - final text = textNode.toPlainText(); - // Determine if an 'underscore' already exists in the text node and only once. - final firstUnderscore = text.indexOf('_'); - final lastUnderscore = text.lastIndexOf('_'); - if (firstUnderscore == -1 || - firstUnderscore != lastUnderscore || - firstUnderscore == selection.start.offset - 1) { - return KeyEventResult.ignored; - } - - // Delete the previous 'underscore', - // update the style of the text surrounded by the two underscores to 'italic', - // and update the cursor position. - final transaction = editorState.transaction - ..deleteText(textNode, firstUnderscore, 1) - ..formatText( - textNode, - firstUnderscore, - selection.end.offset - firstUnderscore - 1, - { - BuiltInAttributeKey.italic: true, - }, - ) - ..afterSelection = Selection.collapsed( - Position( - path: textNode.path, - offset: selection.end.offset - 1, - ), - ); - editorState.apply(transaction); - - return KeyEventResult.handled; -}; diff --git a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml index fc67a9d933..a60ba61f30 100644 --- a/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/example/pubspec.yaml @@ -70,8 +70,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - example.json - - big_document.json - # - images/a_dot_ham.jpeg + - assets/images/icon.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart index 04b2714879..750ba6c689 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/appflowy_editor.dart @@ -31,3 +31,12 @@ export 'src/render/rich_text/default_selectable.dart'; export 'src/render/rich_text/flowy_rich_text.dart'; export 'src/render/selection_menu/selection_menu_widget.dart'; export 'src/l10n/l10n.dart'; +export 'src/render/style/plugin_styles.dart'; +export 'src/render/style/editor_style.dart'; +export 'src/plugins/markdown/encoder/delta_markdown_encoder.dart'; +export 'src/plugins/markdown/encoder/document_markdown_encoder.dart'; +export 'src/plugins/markdown/encoder/parser/node_parser.dart'; +export 'src/plugins/markdown/encoder/parser/text_node_parser.dart'; +export 'src/plugins/markdown/encoder/parser/image_node_parser.dart'; +export 'src/plugins/markdown/decoder/delta_markdown_decoder.dart'; +export 'src/plugins/markdown/document_markdown.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_bn_BN.arb b/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_bn_BN.arb new file mode 100644 index 0000000000..fa58561498 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/l10n/intl_bn_BN.arb @@ -0,0 +1,35 @@ +{ + "@@locale": "bn_BN", + "bold": "বল্ড ফন্ট", + "@bold": {}, + "bulletedList": "বুলেট তালিকা", + "@bulletedList": {}, + "checkbox": "চেকবক্স", + "@checkbox": {}, + "embedCode": "এম্বেড কোড", + "@embedCode": {}, + "heading1": "শিরোনাম 1", + "@heading1": {}, + "heading2": "শিরোনাম 2", + "@heading2": {}, + "heading3": "শিরোনাম 3", + "@heading3": {}, + "highlight": "হাইলাইট", + "@highlight": {}, + "image": "ইমেজ", + "@image": {}, + "italic": "ইটালিক ফন্ট", + "@italic": {}, + "link": "লিঙ্ক", + "@link": {}, + "numberedList": "সংখ্যাযুক্ত তালিকা", + "@numberedList": {}, + "quote": "উদ্ধৃতি", + "@quote": {}, + "strikethrough": "স্ট্রাইকথ্রু", + "@strikethrough": {}, + "text": "পাঠ্য", + "@text": {}, + "underline": "আন্ডারলাইন", + "@underline": {} +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart index b172e78554..2994896b58 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/document.dart @@ -57,8 +57,8 @@ class Document { final parent = nodeAtPath(path.parent); if (parent != null) { - for (final node in nodes) { - parent.insert(node, index: path.last); + for (var i = 0; i < nodes.length; i++) { + parent.insert(nodes.elementAt(i), index: path.last + i); } return true; } @@ -110,6 +110,24 @@ class Document { return true; } + bool get isEmpty { + if (root.children.isEmpty) { + return true; + } + + if (root.children.length > 1) { + return false; + } + + final node = root.children.first; + if (node is TextNode && + (node.delta.isEmpty || node.delta.toPlainText().isEmpty)) { + return true; + } + + return false; + } + Map toJson() { return { 'document': root.toJson(), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart index 5bf1832f73..9aa66d962a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/document/text_delta.dart @@ -50,7 +50,7 @@ class TextInsert extends TextOperation { final result = { 'insert': text, }; - if (_attributes != null) { + if (_attributes != null && _attributes!.isNotEmpty) { result['attributes'] = attributes; } return result; @@ -62,7 +62,7 @@ class TextInsert extends TextOperation { return other is TextInsert && other.text == text && - mapEquals(_attributes, other._attributes); + _mapEquals(_attributes, other._attributes); } @override @@ -87,7 +87,7 @@ class TextRetain extends TextOperation { final result = { 'retain': length, }; - if (_attributes != null) { + if (_attributes != null && _attributes!.isNotEmpty) { result['attributes'] = attributes; } return result; @@ -99,7 +99,7 @@ class TextRetain extends TextOperation { return other is TextRetain && other.length == length && - mapEquals(_attributes, other._attributes); + _mapEquals(_attributes, other._attributes); } @override @@ -181,7 +181,7 @@ class Delta extends Iterable { lastOp.length += textOperation.length; return; } - if (mapEquals(lastOp.attributes, textOperation.attributes)) { + if (_mapEquals(lastOp.attributes, textOperation.attributes)) { if (lastOp is TextInsert && textOperation is TextInsert) { lastOp.text += textOperation.text; return; @@ -539,3 +539,10 @@ class _OpIterator { } } } + +bool _mapEquals(Map? a, Map? b) { + if ((a == null || a.isEmpty) && (b == null || b.isEmpty)) { + return true; + } + return mapEquals(a, b); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart index 297600288d..4df4adb228 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/core/transform/transaction.dart @@ -103,7 +103,7 @@ class Transaction { Map toJson() { final json = {}; if (operations.isNotEmpty) { - json['operations'] = operations.map((o) => o.toJson()); + json['operations'] = operations.map((o) => o.toJson()).toList(); } if (afterSelection != null) { json['after_selection'] = afterSelection!.toJson(); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart index 872dad8e7a..95bab3c231 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/editor_state.dart @@ -59,13 +59,14 @@ class EditorState { /// Stores the selection menu items. List selectionMenuItems = []; - /// Stores the editor style. - EditorStyle editorStyle = EditorStyle.defaultStyle(); - /// Operation stream. Stream get transactionStream => _observer.stream; final StreamController _observer = StreamController.broadcast(); + late ThemeData themeData; + EditorStyle get editorStyle => + themeData.extension() ?? EditorStyle.light; + final UndoManager undoManager = UndoManager(); Selection? _cursorSelection; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart new file mode 100644 index 0000000000..9b8f01aafa --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/extensions/theme_extension.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; + +extension ThemeExtension on ThemeData { + T? extensionOrNull() { + if (extensions.containsKey(T)) { + return extensions[T] as T; + } + return null; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/flutter/overlay.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/flutter/overlay.dart new file mode 100644 index 0000000000..0a91229e0a --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/flutter/overlay.dart @@ -0,0 +1,904 @@ +// TODO: Remove this file until we update the flutter version to 3.5.x +// +// This file is copied from flutter(3.5.x) repo. +// +// We Need to commit(https://github.com/flutter/flutter/pull/113770) to fix the +// overflow issue. + +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:collection'; +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter/widgets.dart'; + +/// A place in an [Overlay] that can contain a widget. +/// +/// Overlay entries are inserted into an [Overlay] using the +/// [OverlayState.insert] or [OverlayState.insertAll] functions. To find the +/// closest enclosing overlay for a given [BuildContext], use the [Overlay.of] +/// function. +/// +/// An overlay entry can be in at most one overlay at a time. To remove an entry +/// from its overlay, call the [remove] function on the overlay entry. +/// +/// Because an [Overlay] uses a [Stack] layout, overlay entries can use +/// [Positioned] and [AnimatedPositioned] to position themselves within the +/// overlay. +/// +/// For example, [Draggable] uses an [OverlayEntry] to show the drag avatar that +/// follows the user's finger across the screen after the drag begins. Using the +/// overlay to display the drag avatar lets the avatar float over the other +/// widgets in the app. As the user's finger moves, draggable calls +/// [markNeedsBuild] on the overlay entry to cause it to rebuild. In its build, +/// the entry includes a [Positioned] with its top and left property set to +/// position the drag avatar near the user's finger. When the drag is over, +/// [Draggable] removes the entry from the overlay to remove the drag avatar +/// from view. +/// +/// By default, if there is an entirely [opaque] entry over this one, then this +/// one will not be included in the widget tree (in particular, stateful widgets +/// within the overlay entry will not be instantiated). To ensure that your +/// overlay entry is still built even if it is not visible, set [maintainState] +/// to true. This is more expensive, so should be done with care. In particular, +/// if widgets in an overlay entry with [maintainState] set to true repeatedly +/// call [State.setState], the user's battery will be drained unnecessarily. +/// +/// [OverlayEntry] is a [ChangeNotifier] that notifies when the widget built by +/// [builder] is mounted or unmounted, whose exact state can be queried by +/// [mounted]. +/// +/// See also: +/// +/// * [Overlay] +/// * [OverlayState] +/// * [WidgetsApp] +/// * [MaterialApp] +class OverlayEntry extends ChangeNotifier { + /// Creates an overlay entry. + /// + /// To insert the entry into an [Overlay], first find the overlay using + /// [Overlay.of] and then call [OverlayState.insert]. To remove the entry, + /// call [remove] on the overlay entry itself. + OverlayEntry({ + required this.builder, + bool opaque = false, + bool maintainState = false, + }) : _opaque = opaque, + _maintainState = maintainState; + + /// This entry will include the widget built by this builder in the overlay at + /// the entry's position. + /// + /// To cause this builder to be called again, call [markNeedsBuild] on this + /// overlay entry. + final WidgetBuilder builder; + + /// Whether this entry occludes the entire overlay. + /// + /// If an entry claims to be opaque, then, for efficiency, the overlay will + /// skip building entries below that entry unless they have [maintainState] + /// set. + bool get opaque => _opaque; + bool _opaque; + set opaque(bool value) { + if (_opaque == value) return; + _opaque = value; + _overlay?._didChangeEntryOpacity(); + } + + /// Whether this entry must be included in the tree even if there is a fully + /// [opaque] entry above it. + /// + /// By default, if there is an entirely [opaque] entry over this one, then this + /// one will not be included in the widget tree (in particular, stateful widgets + /// within the overlay entry will not be instantiated). To ensure that your + /// overlay entry is still built even if it is not visible, set [maintainState] + /// to true. This is more expensive, so should be done with care. In particular, + /// if widgets in an overlay entry with [maintainState] set to true repeatedly + /// call [State.setState], the user's battery will be drained unnecessarily. + /// + /// This is used by the [Navigator] and [Route] objects to ensure that routes + /// are kept around even when in the background, so that [Future]s promised + /// from subsequent routes will be handled properly when they complete. + bool get maintainState => _maintainState; + bool _maintainState; + set maintainState(bool value) { + if (_maintainState == value) return; + _maintainState = value; + assert(_overlay != null); + _overlay!._didChangeEntryOpacity(); + } + + /// Whether the [OverlayEntry] is currently mounted in the widget tree. + /// + /// The [OverlayEntry] notifies its listeners when this value changes. + bool get mounted => _mounted; + bool _mounted = false; + void _updateMounted(bool value) { + if (value == _mounted) { + return; + } + _mounted = value; + notifyListeners(); + } + + OverlayState? _overlay; + final GlobalKey<_OverlayEntryWidgetState> _key = + GlobalKey<_OverlayEntryWidgetState>(); + + /// Remove this entry from the overlay. + /// + /// This should only be called once. + /// + /// This method removes this overlay entry from the overlay immediately. The + /// UI will be updated in the same frame if this method is called before the + /// overlay rebuild in this frame; otherwise, the UI will be updated in the + /// next frame. This means that it is safe to call during builds, but also + /// that if you do call this after the overlay rebuild, the UI will not update + /// until the next frame (i.e. many milliseconds later). + void remove() { + assert(_overlay != null); + final OverlayState overlay = _overlay!; + _overlay = null; + if (!overlay.mounted) return; + + overlay._entries.remove(this); + if (SchedulerBinding.instance.schedulerPhase == + SchedulerPhase.persistentCallbacks) { + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + overlay._markDirty(); + }); + } else { + overlay._markDirty(); + } + } + + /// Cause this entry to rebuild during the next pipeline flush. + /// + /// You need to call this function if the output of [builder] has changed. + void markNeedsBuild() { + _key.currentState?._markNeedsBuild(); + } + + @override + String toString() => + '${describeIdentity(this)}(opaque: $opaque; maintainState: $maintainState)'; +} + +class _OverlayEntryWidget extends StatefulWidget { + const _OverlayEntryWidget({ + required Key key, + required this.entry, + this.tickerEnabled = true, + }) : super(key: key); + + final OverlayEntry entry; + final bool tickerEnabled; + + @override + _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState(); +} + +class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> { + @override + void initState() { + super.initState(); + widget.entry._updateMounted(true); + } + + @override + void dispose() { + widget.entry._updateMounted(false); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TickerMode( + enabled: widget.tickerEnabled, + child: widget.entry.builder(context), + ); + } + + void _markNeedsBuild() { + setState(() {/* the state that changed is in the builder */}); + } +} + +/// A stack of entries that can be managed independently. +/// +/// Overlays let independent child widgets "float" visual elements on top of +/// other widgets by inserting them into the overlay's stack. The overlay lets +/// each of these widgets manage their participation in the overlay using +/// [OverlayEntry] objects. +/// +/// Although you can create an [Overlay] directly, it's most common to use the +/// overlay created by the [Navigator] in a [WidgetsApp] or a [MaterialApp]. The +/// navigator uses its overlay to manage the visual appearance of its routes. +/// +/// The [Overlay] widget uses a custom stack implementation, which is very +/// similar to the [Stack] widget. The main use case of [Overlay] is related to +/// navigation and being able to insert widgets on top of the pages in an app. +/// To simply display a stack of widgets, consider using [Stack] instead. +/// +/// See also: +/// +/// * [OverlayEntry], the class that is used for describing the overlay entries. +/// * [OverlayState], which is used to insert the entries into the overlay. +/// * [WidgetsApp], which inserts an [Overlay] widget indirectly via its [Navigator]. +/// * [MaterialApp], which inserts an [Overlay] widget indirectly via its [Navigator]. +/// * [Stack], which allows directly displaying a stack of widgets. +class Overlay extends StatefulWidget { + /// Creates an overlay. + /// + /// The initial entries will be inserted into the overlay when its associated + /// [OverlayState] is initialized. + /// + /// Rather than creating an overlay, consider using the overlay that is + /// created by the [Navigator] in a [WidgetsApp] or a [MaterialApp] for the application. + const Overlay({ + Key? key, + this.initialEntries = const [], + this.clipBehavior = Clip.hardEdge, + }) : super(key: key); + + /// The entries to include in the overlay initially. + /// + /// These entries are only used when the [OverlayState] is initialized. If you + /// are providing a new [Overlay] description for an overlay that's already in + /// the tree, then the new entries are ignored. + /// + /// To add entries to an [Overlay] that is already in the tree, use + /// [Overlay.of] to obtain the [OverlayState] (or assign a [GlobalKey] to the + /// [Overlay] widget and obtain the [OverlayState] via + /// [GlobalKey.currentState]), and then use [OverlayState.insert] or + /// [OverlayState.insertAll]. + /// + /// To remove an entry from an [Overlay], use [OverlayEntry.remove]. + final List initialEntries; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + final Clip clipBehavior; + + /// The state from the closest instance of this class that encloses the given context. + /// + /// In debug mode, if the `debugRequiredFor` argument is provided then this + /// function will assert that an overlay was found and will throw an exception + /// if not. The exception attempts to explain that the calling [Widget] (the + /// one given by the `debugRequiredFor` argument) needs an [Overlay] to be + /// present to function. + /// + /// Typical usage is as follows: + /// + /// ```dart + /// OverlayState overlay = Overlay.of(context); + /// ``` + /// + /// If `rootOverlay` is set to true, the state from the furthest instance of + /// this class is given instead. Useful for installing overlay entries + /// above all subsequent instances of [Overlay]. + /// + /// This method can be expensive (it walks the element tree). + static OverlayState? of( + BuildContext context, { + bool rootOverlay = false, + Widget? debugRequiredFor, + }) { + final OverlayState? result = rootOverlay + ? context.findRootAncestorStateOfType() + : context.findAncestorStateOfType(); + assert(() { + if (debugRequiredFor != null && result == null) { + final List information = [ + ErrorSummary('No Overlay widget found.'), + ErrorDescription( + '${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.'), + ErrorHint( + 'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.'), + DiagnosticsProperty( + 'The specific widget that failed to find an overlay was', + debugRequiredFor, + style: DiagnosticsTreeStyle.errorProperty), + if (context.widget != debugRequiredFor) + context.describeElement( + 'The context from which that widget was searching for an overlay was'), + ]; + + throw FlutterError.fromParts(information); + } + return true; + }()); + return result; + } + + @override + OverlayState createState() => OverlayState(); +} + +/// The current state of an [Overlay]. +/// +/// Used to insert [OverlayEntry]s into the overlay using the [insert] and +/// [insertAll] functions. +class OverlayState extends State with TickerProviderStateMixin { + final List _entries = []; + + @override + void initState() { + super.initState(); + insertAll(widget.initialEntries); + } + + int _insertionIndex(OverlayEntry? below, OverlayEntry? above) { + assert(above == null || below == null); + if (below != null) return _entries.indexOf(below); + if (above != null) return _entries.indexOf(above) + 1; + return _entries.length; + } + + /// Insert the given entry into the overlay. + /// + /// If `below` is non-null, the entry is inserted just below `below`. + /// If `above` is non-null, the entry is inserted just above `above`. + /// Otherwise, the entry is inserted on top. + /// + /// It is an error to specify both `above` and `below`. + void insert(OverlayEntry entry, {OverlayEntry? below, OverlayEntry? above}) { + assert(_debugVerifyInsertPosition(above, below)); + assert(!_entries.contains(entry), + 'The specified entry is already present in the Overlay.'); + assert(entry._overlay == null, + 'The specified entry is already present in another Overlay.'); + entry._overlay = this; + setState(() { + _entries.insert(_insertionIndex(below, above), entry); + }); + } + + /// Insert all the entries in the given iterable. + /// + /// If `below` is non-null, the entries are inserted just below `below`. + /// If `above` is non-null, the entries are inserted just above `above`. + /// Otherwise, the entries are inserted on top. + /// + /// It is an error to specify both `above` and `below`. + void insertAll(Iterable entries, + {OverlayEntry? below, OverlayEntry? above}) { + assert(_debugVerifyInsertPosition(above, below)); + assert( + entries.every((OverlayEntry entry) => !_entries.contains(entry)), + 'One or more of the specified entries are already present in the Overlay.', + ); + assert( + entries.every((OverlayEntry entry) => entry._overlay == null), + 'One or more of the specified entries are already present in another Overlay.', + ); + if (entries.isEmpty) return; + for (final OverlayEntry entry in entries) { + assert(entry._overlay == null); + entry._overlay = this; + } + setState(() { + _entries.insertAll(_insertionIndex(below, above), entries); + }); + } + + bool _debugVerifyInsertPosition(OverlayEntry? above, OverlayEntry? below, + {Iterable? newEntries}) { + assert( + above == null || below == null, + 'Only one of `above` and `below` may be specified.', + ); + assert( + above == null || + (above._overlay == this && + _entries.contains(above) && + (newEntries?.contains(above) ?? true)), + 'The provided entry used for `above` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.', + ); + assert( + below == null || + (below._overlay == this && + _entries.contains(below) && + (newEntries?.contains(below) ?? true)), + 'The provided entry used for `below` must be present in the Overlay${newEntries != null ? ' and in the `newEntriesList`' : ''}.', + ); + return true; + } + + /// Remove all the entries listed in the given iterable, then reinsert them + /// into the overlay in the given order. + /// + /// Entries mention in `newEntries` but absent from the overlay are inserted + /// as if with [insertAll]. + /// + /// Entries not mentioned in `newEntries` but present in the overlay are + /// positioned as a group in the resulting list relative to the entries that + /// were moved, as specified by one of `below` or `above`, which, if + /// specified, must be one of the entries in `newEntries`: + /// + /// If `below` is non-null, the group is positioned just below `below`. + /// If `above` is non-null, the group is positioned just above `above`. + /// Otherwise, the group is left on top, with all the rearranged entries + /// below. + /// + /// It is an error to specify both `above` and `below`. + void rearrange(Iterable newEntries, + {OverlayEntry? below, OverlayEntry? above}) { + final List newEntriesList = newEntries is List + ? newEntries + : newEntries.toList(growable: false); + assert( + _debugVerifyInsertPosition(above, below, newEntries: newEntriesList)); + assert( + newEntriesList.every((OverlayEntry entry) => + entry._overlay == null || entry._overlay == this), + 'One or more of the specified entries are already present in another Overlay.', + ); + assert( + newEntriesList.every((OverlayEntry entry) => + _entries.indexOf(entry) == _entries.lastIndexOf(entry)), + 'One or more of the specified entries are specified multiple times.', + ); + if (newEntriesList.isEmpty) return; + if (listEquals(_entries, newEntriesList)) return; + final LinkedHashSet old = + LinkedHashSet.of(_entries); + for (final OverlayEntry entry in newEntriesList) { + entry._overlay ??= this; + } + setState(() { + _entries.clear(); + _entries.addAll(newEntriesList); + old.removeAll(newEntriesList); + _entries.insertAll(_insertionIndex(below, above), old); + }); + } + + void _markDirty() { + if (mounted) { + setState(() {}); + } + } + + /// (DEBUG ONLY) Check whether a given entry is visible (i.e., not behind an + /// opaque entry). + /// + /// This is an O(N) algorithm, and should not be necessary except for debug + /// asserts. To avoid people depending on it, this function is implemented + /// only in debug mode, and always returns false in release mode. + bool debugIsVisible(OverlayEntry entry) { + bool result = false; + assert(_entries.contains(entry)); + assert(() { + for (int i = _entries.length - 1; i > 0; i -= 1) { + final OverlayEntry candidate = _entries[i]; + if (candidate == entry) { + result = true; + break; + } + if (candidate.opaque) break; + } + return true; + }()); + return result; + } + + void _didChangeEntryOpacity() { + setState(() { + // We use the opacity of the entry in our build function, which means we + // our state has changed. + }); + } + + @override + Widget build(BuildContext context) { + // This list is filled backwards and then reversed below before + // it is added to the tree. + final List children = []; + bool onstage = true; + int onstageCount = 0; + for (int i = _entries.length - 1; i >= 0; i -= 1) { + final OverlayEntry entry = _entries[i]; + if (onstage) { + onstageCount += 1; + children.add(_OverlayEntryWidget( + key: entry._key, + entry: entry, + )); + if (entry.opaque) onstage = false; + } else if (entry.maintainState) { + children.add(_OverlayEntryWidget( + key: entry._key, + entry: entry, + tickerEnabled: false, + )); + } + } + return _Theatre( + skipCount: children.length - onstageCount, + clipBehavior: widget.clipBehavior, + children: children.reversed.toList(growable: false), + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + // TODO(jacobr): use IterableProperty instead as that would + // provide a slightly more consistent string summary of the List. + properties + .add(DiagnosticsProperty>('entries', _entries)); + } +} + +/// Special version of a [Stack], that doesn't layout and render the first +/// [skipCount] children. +/// +/// The first [skipCount] children are considered "offstage". +class _Theatre extends MultiChildRenderObjectWidget { + _Theatre({ + Key? key, + this.skipCount = 0, + this.clipBehavior = Clip.hardEdge, + List children = const [], + }) : assert(skipCount >= 0), + assert(children.length >= skipCount), + super(key: key, children: children); + + final int skipCount; + + final Clip clipBehavior; + + @override + _TheatreElement createElement() => _TheatreElement(this); + + @override + _RenderTheatre createRenderObject(BuildContext context) { + return _RenderTheatre( + skipCount: skipCount, + textDirection: Directionality.of(context), + clipBehavior: clipBehavior, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderTheatre renderObject) { + renderObject + ..skipCount = skipCount + ..textDirection = Directionality.of(context) + ..clipBehavior = clipBehavior; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IntProperty('skipCount', skipCount)); + } +} + +class _TheatreElement extends MultiChildRenderObjectElement { + _TheatreElement(_Theatre widget) : super(widget); + + @override + _RenderTheatre get renderObject => super.renderObject as _RenderTheatre; + + @override + void debugVisitOnstageChildren(ElementVisitor visitor) { + final _Theatre theatre = widget as _Theatre; + assert(children.length >= theatre.skipCount); + children.skip(theatre.skipCount).forEach(visitor); + } +} + +class _RenderTheatre extends RenderBox + with ContainerRenderObjectMixin { + _RenderTheatre({ + List? children, + required TextDirection textDirection, + int skipCount = 0, + Clip clipBehavior = Clip.hardEdge, + }) : assert(skipCount >= 0), + _textDirection = textDirection, + _skipCount = skipCount, + _clipBehavior = clipBehavior { + addAll(children); + } + + bool _hasVisualOverflow = false; + + @override + void setupParentData(RenderBox child) { + if (child.parentData is! StackParentData) { + child.parentData = StackParentData(); + } + } + + Alignment? _resolvedAlignment; + + void _resolve() { + if (_resolvedAlignment != null) return; + _resolvedAlignment = AlignmentDirectional.topStart.resolve(textDirection); + } + + void _markNeedResolution() { + _resolvedAlignment = null; + markNeedsLayout(); + } + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (_textDirection == value) return; + _textDirection = value; + _markNeedResolution(); + } + + int get skipCount => _skipCount; + int _skipCount; + set skipCount(int value) { + if (_skipCount != value) { + _skipCount = value; + markNeedsLayout(); + } + } + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + RenderBox? get _firstOnstageChild { + if (skipCount == super.childCount) { + return null; + } + RenderBox? child = super.firstChild; + for (int toSkip = skipCount; toSkip > 0; toSkip--) { + final StackParentData childParentData = + child!.parentData! as StackParentData; + child = childParentData.nextSibling; + assert(child != null); + } + return child; + } + + RenderBox? get _lastOnstageChild => + skipCount == super.childCount ? null : lastChild; + + int get _onstageChildCount => childCount - skipCount; + + @override + double computeMinIntrinsicWidth(double height) { + return RenderStack.getIntrinsicDimension(_firstOnstageChild, + (RenderBox child) => child.getMinIntrinsicWidth(height)); + } + + @override + double computeMaxIntrinsicWidth(double height) { + return RenderStack.getIntrinsicDimension(_firstOnstageChild, + (RenderBox child) => child.getMaxIntrinsicWidth(height)); + } + + @override + double computeMinIntrinsicHeight(double width) { + return RenderStack.getIntrinsicDimension(_firstOnstageChild, + (RenderBox child) => child.getMinIntrinsicHeight(width)); + } + + @override + double computeMaxIntrinsicHeight(double width) { + return RenderStack.getIntrinsicDimension(_firstOnstageChild, + (RenderBox child) => child.getMaxIntrinsicHeight(width)); + } + + @override + double? computeDistanceToActualBaseline(TextBaseline baseline) { + assert(!debugNeedsLayout); + double? result; + RenderBox? child = _firstOnstageChild; + while (child != null) { + assert(!child.debugNeedsLayout); + final StackParentData childParentData = + child.parentData! as StackParentData; + double? candidate = child.getDistanceToActualBaseline(baseline); + if (candidate != null) { + candidate += childParentData.offset.dy; + if (result != null) { + result = math.min(result, candidate); + } else { + result = candidate; + } + } + child = childParentData.nextSibling; + } + return result; + } + + @override + bool get sizedByParent => true; + + @override + Size computeDryLayout(BoxConstraints constraints) { + assert(constraints.biggest.isFinite); + return constraints.biggest; + } + + @override + void performLayout() { + _hasVisualOverflow = false; + + if (_onstageChildCount == 0) { + return; + } + + _resolve(); + assert(_resolvedAlignment != null); + + // Same BoxConstraints as used by RenderStack for StackFit.expand. + final BoxConstraints nonPositionedConstraints = + BoxConstraints.tight(constraints.biggest); + + RenderBox? child = _firstOnstageChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + + if (!childParentData.isPositioned) { + child.layout(nonPositionedConstraints, parentUsesSize: true); + childParentData.offset = + _resolvedAlignment!.alongOffset(size - child.size as Offset); + } else { + _hasVisualOverflow = RenderStack.layoutPositionedChild( + child, childParentData, size, _resolvedAlignment!) || + _hasVisualOverflow; + } + + assert(child.parentData == childParentData); + child = childParentData.nextSibling; + } + } + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = _lastOnstageChild; + for (int i = 0; i < _onstageChildCount; i++) { + assert(child != null); + final StackParentData childParentData = + child!.parentData! as StackParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - childParentData.offset); + return child!.hitTest(result, position: transformed); + }, + ); + if (isHit) return true; + child = childParentData.previousSibling; + } + return false; + } + + @protected + void paintStack(PaintingContext context, Offset offset) { + RenderBox? child = _firstOnstageChild; + while (child != null) { + final StackParentData childParentData = + child.parentData! as StackParentData; + context.paintChild(child, childParentData.offset + offset); + child = childParentData.nextSibling; + } + } + + @override + void paint(PaintingContext context, Offset offset) { + _hasVisualOverflow = true; + if (_hasVisualOverflow && clipBehavior != Clip.none) { + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + Offset.zero & size, + paintStack, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + } else { + _clipRectLayer.layer = null; + paintStack(context, offset); + } + } + + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRectLayer.layer = null; + super.dispose(); + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = _firstOnstageChild; + while (child != null) { + visitor(child); + final StackParentData childParentData = + child.parentData! as StackParentData; + child = childParentData.nextSibling; + } + } + + @override + Rect? describeApproximatePaintClip(RenderObject child) => + _hasVisualOverflow ? Offset.zero & size : null; + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(IntProperty('skipCount', skipCount)); + properties.add(EnumProperty('textDirection', textDirection)); + } + + @override + List debugDescribeChildren() { + final List offstageChildren = []; + final List onstageChildren = []; + + int count = 1; + bool onstage = false; + RenderBox? child = firstChild; + final RenderBox? firstOnstageChild = _firstOnstageChild; + while (child != null) { + if (child == firstOnstageChild) { + onstage = true; + count = 1; + } + + if (onstage) { + onstageChildren.add( + child.toDiagnosticsNode( + name: 'onstage $count', + ), + ); + } else { + offstageChildren.add( + child.toDiagnosticsNode( + name: 'offstage $count', + style: DiagnosticsTreeStyle.offstage, + ), + ); + } + + final StackParentData childParentData = + child.parentData! as StackParentData; + child = childParentData.nextSibling; + count += 1; + } + + return [ + ...onstageChildren, + if (offstageChildren.isNotEmpty) + ...offstageChildren + else + DiagnosticsNode.message( + 'no offstage children', + style: DiagnosticsTreeStyle.offstage, + ), + ]; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/html_converter.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/html_converter.dart index b433657d4e..f20d3d8a9a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/html_converter.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/infra/html_converter.dart @@ -241,6 +241,8 @@ class HTMLToNodesConverter { } else if (element.localName == HTMLTag.del) { delta.insert(element.text, attributes: {BuiltInAttributeKey.strikethrough: true}); + } else if (element.localName == HTMLTag.code) { + delta.insert(element.text, attributes: {BuiltInAttributeKey.code: true}); } else { delta.insert(element.text); } @@ -276,11 +278,13 @@ class HTMLToNodesConverter { } } - final textNode = TextNode(delta: delta, attributes: attributes); - if (isCheckbox) { - textNode.attributes["subtype"] = BuiltInAttributeKey.checkbox; - textNode.attributes["checkbox"] = checked; - } + final textNode = TextNode(delta: delta, attributes: { + if (attributes != null) ...attributes, + if (isCheckbox) ...{ + BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, + BuiltInAttributeKey.checkbox: checked, + } + }); return textNode; } @@ -557,6 +561,17 @@ class NodesToHTMLConverter { final strong = html.Element.tag(HTMLTag.del); strong.append(html.Text(op.text)); childNodes.add(strong); + } else if (attributes.length == 1 && + attributes[BuiltInAttributeKey.code] == true) { + final code = html.Element.tag(HTMLTag.code); + code.append(html.Text(op.text)); + childNodes.add(code); + } else if (attributes.length == 1 && + attributes[BuiltInAttributeKey.href] != null) { + final anchor = html.Element.tag(HTMLTag.anchor); + anchor.attributes["href"] = attributes[BuiltInAttributeKey.href]; + anchor.append(html.Text(op.text)); + childNodes.add(anchor); } else { final span = html.Element.tag(HTMLTag.span); final cssString = _attributesToCssStyle(attributes); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart index f98e70ad28..16fd7b9b6b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_all.dart @@ -15,6 +15,7 @@ import 'package:intl/intl.dart'; import 'package:intl/message_lookup_by_library.dart'; import 'package:intl/src/intl_helpers.dart'; +import 'messages_bn_BN.dart' as messages_bn_bn; import 'messages_ca.dart' as messages_ca; import 'messages_cs-CZ.dart' as messages_cs_cz; import 'messages_de-DE.dart' as messages_de_de; @@ -39,6 +40,7 @@ import 'messages_zh-TW.dart' as messages_zh_tw; typedef Future LibraryLoader(); Map _deferredLibraries = { + 'bn_BN': () => new Future.value(null), 'ca': () => new Future.value(null), 'cs_CZ': () => new Future.value(null), 'de_DE': () => new Future.value(null), @@ -64,6 +66,8 @@ Map _deferredLibraries = { MessageLookupByLibrary? _findExact(String localeName) { switch (localeName) { + case 'bn_BN': + return messages_bn_bn.messages; case 'ca': return messages_ca.messages; case 'cs_CZ': diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_bn_BN.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_bn_BN.dart new file mode 100644 index 0000000000..38e1cae590 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/intl/messages_bn_BN.dart @@ -0,0 +1,43 @@ +// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart +// This is a library that provides messages for a bn_BN locale. All the +// messages from the main program should be duplicated here with the same +// function name. + +// Ignore issues from commonly used lints in this file. +// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new +// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering +// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases +// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes +// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes + +import 'package:intl/intl.dart'; +import 'package:intl/message_lookup_by_library.dart'; + +final messages = new MessageLookup(); + +typedef String MessageIfAbsent(String messageStr, List args); + +class MessageLookup extends MessageLookupByLibrary { + String get localeName => 'bn_BN'; + + final messages = _notInlinedMessages(_notInlinedMessages); + static Map _notInlinedMessages(_) => { + "bold": MessageLookupByLibrary.simpleMessage("বল্ড ফন্ট"), + "bulletedList": MessageLookupByLibrary.simpleMessage("বুলেট তালিকা"), + "checkbox": MessageLookupByLibrary.simpleMessage("চেকবক্স"), + "embedCode": MessageLookupByLibrary.simpleMessage("এম্বেড কোড"), + "heading1": MessageLookupByLibrary.simpleMessage("শিরোনাম 1"), + "heading2": MessageLookupByLibrary.simpleMessage("শিরোনাম 2"), + "heading3": MessageLookupByLibrary.simpleMessage("শিরোনাম 3"), + "highlight": MessageLookupByLibrary.simpleMessage("হাইলাইট"), + "image": MessageLookupByLibrary.simpleMessage("ইমেজ"), + "italic": MessageLookupByLibrary.simpleMessage("ইটালিক ফন্ট"), + "link": MessageLookupByLibrary.simpleMessage("লিঙ্ক"), + "numberedList": + MessageLookupByLibrary.simpleMessage("সংখ্যাযুক্ত তালিকা"), + "quote": MessageLookupByLibrary.simpleMessage("উদ্ধৃতি"), + "strikethrough": MessageLookupByLibrary.simpleMessage("স্ট্রাইকথ্রু"), + "text": MessageLookupByLibrary.simpleMessage("পাঠ্য"), + "underline": MessageLookupByLibrary.simpleMessage("আন্ডারলাইন") + }; +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart index 7c45b8ae85..0d464022d2 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/l10n/l10n.dart @@ -219,6 +219,7 @@ class AppLocalizationDelegate List get supportedLocales { return const [ Locale.fromSubtags(languageCode: 'en'), + Locale.fromSubtags(languageCode: 'bn', countryCode: 'BN'), Locale.fromSubtags(languageCode: 'ca'), Locale.fromSubtags(languageCode: 'cs', countryCode: 'CZ'), Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/delta_markdown_decoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/delta_markdown_decoder.dart new file mode 100644 index 0000000000..e015546989 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/delta_markdown_decoder.dart @@ -0,0 +1,66 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/src/core/document/attributes.dart'; +import 'package:appflowy_editor/src/core/document/text_delta.dart'; +import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart'; +import 'package:markdown/markdown.dart' as md; + +class DeltaMarkdownDecoder extends Converter + with md.NodeVisitor { + final _delta = Delta(); + final Attributes _attributes = {}; + + @override + Delta convert(String input) { + final document = + md.Document(extensionSet: md.ExtensionSet.gitHubWeb).parseInline(input); + for (final node in document) { + node.accept(this); + } + return _delta; + } + + @override + void visitElementAfter(md.Element element) { + _removeAttributeKey(element); + } + + @override + bool visitElementBefore(md.Element element) { + _addAttributeKey(element); + return true; + } + + @override + void visitText(md.Text text) { + _delta.add(TextInsert(text.text, attributes: {..._attributes})); + } + + void _addAttributeKey(md.Element element) { + if (element.tag == 'strong') { + _attributes[BuiltInAttributeKey.bold] = true; + } else if (element.tag == 'em') { + _attributes[BuiltInAttributeKey.italic] = true; + } else if (element.tag == 'code') { + _attributes[BuiltInAttributeKey.code] = true; + } else if (element.tag == 'del') { + _attributes[BuiltInAttributeKey.strikethrough] = true; + } else if (element.tag == 'a') { + _attributes[BuiltInAttributeKey.href] = element.attributes['href']; + } + } + + void _removeAttributeKey(md.Element element) { + if (element.tag == 'strong') { + _attributes.remove(BuiltInAttributeKey.bold); + } else if (element.tag == 'em') { + _attributes.remove(BuiltInAttributeKey.italic); + } else if (element.tag == 'code') { + _attributes.remove(BuiltInAttributeKey.code); + } else if (element.tag == 'del') { + _attributes.remove(BuiltInAttributeKey.strikethrough); + } else if (element.tag == 'a') { + _attributes.remove(BuiltInAttributeKey.href); + } + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart new file mode 100644 index 0000000000..257cfabb28 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/decoder/document_markdown_decoder.dart @@ -0,0 +1,91 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; + +class DocumentMarkdownDecoder extends Converter { + @override + Document convert(String input) { + final lines = input.split('\n'); + final document = Document.empty(); + + var i = 0; + for (final line in lines) { + document.insert([i++], [_convertLineToNode(line)]); + } + + return document; + } + + Node _convertLineToNode(String text) { + final decoder = DeltaMarkdownDecoder(); + // Heading Style + if (text.startsWith('### ')) { + return TextNode( + delta: decoder.convert(text.substring(4)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h3, + }, + ); + } else if (text.startsWith('## ')) { + return TextNode( + delta: decoder.convert(text.substring(3)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h2, + }, + ); + } else if (text.startsWith('# ')) { + return TextNode( + delta: decoder.convert(text.substring(2)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h1, + }, + ); + } else if (text.startsWith('- [ ] ')) { + return TextNode( + delta: decoder.convert(text.substring(6)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, + BuiltInAttributeKey.checkbox: false, + }, + ); + } else if (text.startsWith('- [x] ')) { + return TextNode( + delta: decoder.convert(text.substring(6)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, + BuiltInAttributeKey.checkbox: true, + }, + ); + } else if (text.startsWith('> ')) { + return TextNode( + delta: decoder.convert(text.substring(2)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote, + }, + ); + } else if (text.startsWith('- ') || text.startsWith('* ')) { + return TextNode( + delta: decoder.convert(text.substring(2)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList, + }, + ); + } else if (text.startsWith('> ')) { + return TextNode( + delta: decoder.convert(text.substring(2)), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote, + }, + ); + } + + if (text.isNotEmpty) { + return TextNode(delta: decoder.convert(text)); + } + + return TextNode(delta: Delta()); + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown.dart new file mode 100644 index 0000000000..224f9b6ccd --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/document_markdown.dart @@ -0,0 +1,28 @@ +library delta_markdown; + +import 'dart:convert'; + +import 'package:appflowy_editor/src/core/document/document.dart'; +import 'package:appflowy_editor/src/plugins/markdown/decoder/document_markdown_decoder.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/document_markdown_encoder.dart'; + +/// Codec used to convert between Markdown and AppFlowy Editor Document. +const AppFlowyEditorMarkdownCodec _kCodec = AppFlowyEditorMarkdownCodec(); + +Document markdownToDocument(String markdown) { + return _kCodec.decode(markdown); +} + +String documentToMarkdown(Document document) { + return _kCodec.encode(document); +} + +class AppFlowyEditorMarkdownCodec extends Codec { + const AppFlowyEditorMarkdownCodec(); + + @override + Converter get decoder => DocumentMarkdownDecoder(); + + @override + Converter get encoder => DocumentMarkdownEncoder(); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/delta_markdown_encoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/delta_markdown_encoder.dart new file mode 100644 index 0000000000..5c8bd9ebf9 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/delta_markdown_encoder.dart @@ -0,0 +1,88 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; + +/// A [Delta] encoder that encodes a [Delta] to Markdown. +/// +/// Only support inline styles, like bold, italic, underline, strike, code. +class DeltaMarkdownEncoder extends Converter { + @override + String convert(Delta input) { + final buffer = StringBuffer(); + final iterator = input.iterator; + while (iterator.moveNext()) { + final op = iterator.current; + if (op is TextInsert) { + final attributes = op.attributes; + if (attributes != null) { + buffer.write(_prefixSyntax(attributes)); + buffer.write(op.text); + buffer.write(_suffixSyntax(attributes)); + } else { + buffer.write(op.text); + } + } + } + return buffer.toString(); + } + + String _prefixSyntax(Attributes attributes) { + var syntax = ''; + + if (attributes[BuiltInAttributeKey.bold] == true && + attributes[BuiltInAttributeKey.italic] == true) { + syntax += '***'; + } else if (attributes[BuiltInAttributeKey.bold] == true) { + syntax += '**'; + } else if (attributes[BuiltInAttributeKey.italic] == true) { + syntax += '_'; + } + + if (attributes[BuiltInAttributeKey.strikethrough] == true) { + syntax += '~~'; + } + if (attributes[BuiltInAttributeKey.underline] == true) { + syntax += ''; + } + if (attributes[BuiltInAttributeKey.code] == true) { + syntax += '`'; + } + + if (attributes[BuiltInAttributeKey.href] != null) { + syntax += '['; + } + + return syntax; + } + + String _suffixSyntax(Attributes attributes) { + var syntax = ''; + + if (attributes[BuiltInAttributeKey.href] != null) { + syntax += '](${attributes[BuiltInAttributeKey.href]})'; + } + + if (attributes[BuiltInAttributeKey.code] == true) { + syntax += '`'; + } + + if (attributes[BuiltInAttributeKey.underline] == true) { + syntax += ''; + } + + if (attributes[BuiltInAttributeKey.strikethrough] == true) { + syntax += '~~'; + } + + if (attributes[BuiltInAttributeKey.bold] == true && + attributes[BuiltInAttributeKey.italic] == true) { + syntax += '***'; + } else if (attributes[BuiltInAttributeKey.bold] == true) { + syntax += '**'; + } else if (attributes[BuiltInAttributeKey.italic] == true) { + syntax += '_'; + } + + return syntax; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/document_markdown_encoder.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/document_markdown_encoder.dart new file mode 100644 index 0000000000..52c2bd3756 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/document_markdown_encoder.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/src/core/document/document.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/image_node_parser.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/text_node_parser.dart'; + +class DocumentMarkdownEncoder extends Converter { + DocumentMarkdownEncoder({ + this.parsers = const [ + TextNodeParser(), + ImageNodeParser(), + ], + }); + + final List parsers; + + @override + String convert(Document input) { + final buffer = StringBuffer(); + for (final node in input.root.children) { + NodeParser? parser = + parsers.firstWhereOrNull((element) => element.id == node.type); + if (parser != null) { + buffer.write(parser.transform(node)); + } + } + return buffer.toString(); + } +} + +extension IterableExtension on Iterable { + T? firstWhereOrNull(bool Function(T element) test) { + for (var element in this) { + if (test(element)) return element; + } + return null; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/image_node_parser.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/image_node_parser.dart new file mode 100644 index 0000000000..5db9f1b558 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/image_node_parser.dart @@ -0,0 +1,14 @@ +import 'package:appflowy_editor/src/core/document/node.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart'; + +class ImageNodeParser extends NodeParser { + const ImageNodeParser(); + + @override + String get id => 'image'; + + @override + String transform(Node node) { + return '![](${node.attributes['image_src']})'; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/node_parser.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/node_parser.dart new file mode 100644 index 0000000000..9cbdabfbb9 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/node_parser.dart @@ -0,0 +1,8 @@ +import 'package:appflowy_editor/src/core/document/node.dart'; + +abstract class NodeParser { + const NodeParser(); + + String get id; + String transform(Node node); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/text_node_parser.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/text_node_parser.dart new file mode 100644 index 0000000000..8857310876 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/plugins/markdown/encoder/parser/text_node_parser.dart @@ -0,0 +1,64 @@ +import 'package:appflowy_editor/src/core/document/node.dart'; +import 'package:appflowy_editor/src/core/legacy/built_in_attribute_keys.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/delta_markdown_encoder.dart'; +import 'package:appflowy_editor/src/plugins/markdown/encoder/parser/node_parser.dart'; + +class TextNodeParser extends NodeParser { + const TextNodeParser(); + + @override + String get id => 'text'; + + @override + String transform(Node node) { + assert(node is TextNode); + final textNode = node as TextNode; + final markdown = DeltaMarkdownEncoder().convert(textNode.delta); + final attributes = textNode.attributes; + var result = markdown; + var suffix = '\n'; + if (attributes.isNotEmpty && + attributes.containsKey(BuiltInAttributeKey.subtype)) { + final subtype = attributes[BuiltInAttributeKey.subtype]; + if (node.next == null) { + suffix = ''; + } + if (subtype == 'heading') { + final heading = attributes[BuiltInAttributeKey.heading]; + if (heading == 'h1') { + result = '# $markdown'; + } else if (heading == 'h2') { + result = '## $markdown'; + } else if (heading == 'h3') { + result = '### $markdown'; + } else if (heading == 'h4') { + result = '#### $markdown'; + } else if (heading == 'h5') { + result = '##### $markdown'; + } else if (heading == 'h6') { + result = '###### $markdown'; + } + } else if (subtype == 'quote') { + result = '> $markdown'; + } else if (subtype == 'code-block') { + result = '```\n$markdown\n```'; + } else if (subtype == 'bulleted-list') { + result = '* $markdown'; + } else if (subtype == 'number-list') { + final number = attributes['number']; + result = '$number. $markdown'; + } else if (subtype == 'checkbox') { + if (attributes[BuiltInAttributeKey.checkbox] == true) { + result = '- [x] $markdown'; + } else { + result = '- [ ] $markdown'; + } + } + } else { + if (node.next == null) { + suffix = ''; + } + } + return '$result$suffix'; + } +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart index 6087606c56..a8909d16c5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/image/image_upload_widget.dart @@ -1,9 +1,8 @@ -import 'dart:collection'; - import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:flutter/material.dart'; OverlayEntry? _imageUploadMenu; @@ -22,6 +21,7 @@ void showImageUploadMenu( left: menuService.topLeft.dx, child: Material( child: ImageUploadMenu( + editorState: editorState, onSubmitted: (text) { // _dismissImageUploadMenu(); editorState.insertImageNode(text); @@ -55,10 +55,12 @@ class ImageUploadMenu extends StatefulWidget { Key? key, required this.onSubmitted, required this.onUpload, + this.editorState, }) : super(key: key); final void Function(String text) onSubmitted; final void Function(String text) onUpload; + final EditorState? editorState; @override State createState() => _ImageUploadMenuState(); @@ -68,6 +70,8 @@ class _ImageUploadMenuState extends State { final _textEditingController = TextEditingController(); final _focusNode = FocusNode(); + EditorStyle? get style => widget.editorState?.editorStyle; + @override void initState() { super.initState(); @@ -86,7 +90,7 @@ class _ImageUploadMenuState extends State { width: 300, padding: const EdgeInsets.all(24.0), decoration: BoxDecoration( - color: Colors.white, + color: style?.selectionMenuBackgroundColor ?? Colors.white, boxShadow: [ BoxShadow( blurRadius: 5, @@ -94,7 +98,7 @@ class _ImageUploadMenuState extends State { color: Colors.black.withOpacity(0.1), ), ], - borderRadius: BorderRadius.circular(6.0), + // borderRadius: BorderRadius.circular(6.0), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -110,12 +114,12 @@ class _ImageUploadMenuState extends State { } Widget _buildHeader(BuildContext context) { - return const Text( + return Text( 'URL Image', textAlign: TextAlign.left, style: TextStyle( fontSize: 14.0, - color: Colors.black, + color: style?.selectionMenuItemTextColor ?? Colors.black, fontWeight: FontWeight.w500, ), ); @@ -185,12 +189,12 @@ extension on EditorState { } final imageNode = Node( type: 'image', - children: LinkedList(), attributes: { 'image_src': src, 'align': 'center', }, ); + final transaction = this.transaction; transaction.insertNode( selection.start.path, imageNode, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart index 3a1785391b..58e8111793 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/link_menu/link_menu.dart @@ -1,10 +1,13 @@ +import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:flutter/material.dart'; class LinkMenu extends StatefulWidget { const LinkMenu({ Key? key, this.linkText, + this.editorState, required this.onSubmitted, required this.onOpenLink, required this.onCopyLink, @@ -13,6 +16,7 @@ class LinkMenu extends StatefulWidget { }) : super(key: key); final String? linkText; + final EditorState? editorState; final void Function(String text) onSubmitted; final VoidCallback onOpenLink; final VoidCallback onCopyLink; @@ -27,6 +31,8 @@ class _LinkMenuState extends State { final _textEditingController = TextEditingController(); final _focusNode = FocusNode(); + EditorStyle? get style => widget.editorState?.editorStyle; + @override void initState() { super.initState(); @@ -48,7 +54,7 @@ class _LinkMenuState extends State { width: 350, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: style?.selectionMenuBackgroundColor ?? Colors.white, boxShadow: [ BoxShadow( blurRadius: 5, @@ -71,17 +77,19 @@ class _LinkMenuState extends State { if (widget.linkText != null) ...[ _buildIconButton( iconName: 'link', + color: style?.selectionMenuItemIconColor, text: 'Open link', onPressed: widget.onOpenLink, ), _buildIconButton( iconName: 'copy', - color: Colors.black, + color: style?.selectionMenuItemIconColor, text: 'Copy link', onPressed: widget.onCopyLink, ), _buildIconButton( iconName: 'delete', + color: style?.selectionMenuItemIconColor, text: 'Remove link', onPressed: widget.onRemoveLink, ), @@ -154,8 +162,8 @@ class _LinkMenuState extends State { label: Text( text, textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, + style: TextStyle( + color: style?.selectionMenuItemTextColor ?? Colors.black, fontSize: 14.0, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart index a0a65d9583..7fb2cee2cb 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/built_in_text_widget.dart @@ -10,56 +10,6 @@ abstract class BuiltInTextWidget extends StatefulWidget { TextNode get textNode; } -mixin BuiltInStyleMixin on State { - EdgeInsets get padding { - final padding = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'padding', - ); - if (padding is EdgeInsets) { - return padding; - } - return const EdgeInsets.all(0); - } - - TextStyle get textStyle { - final textStyle = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'textStyle', - ); - if (textStyle is TextStyle) { - return textStyle; - } - return const TextStyle(); - } - - Size? get iconSize { - final iconSize = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'iconSize', - ); - if (iconSize is Size) { - return iconSize; - } - return const Size.square(18.0); - } - - EdgeInsets? get iconPadding { - final iconPadding = widget.editorState.editorStyle.style( - widget.editorState, - widget.textNode, - 'iconPadding', - ); - if (iconPadding is EdgeInsets) { - return iconPadding; - } - return const EdgeInsets.all(0); - } -} - mixin BuiltInTextWidgetMixin on State implements DefaultSelectable { @override diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart index 36e3568684..3f6927df4c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/bulleted_list_text.dart @@ -1,13 +1,14 @@ import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart'; import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; +import 'package:appflowy_editor/src/render/style/plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class BulletedListTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -45,11 +46,7 @@ class BulletedListTextNodeWidget extends BuiltInTextWidget { // customize class _BulletedListTextNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override final iconKey = GlobalKey(); @@ -64,6 +61,25 @@ class _BulletedListTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + BulletedListPluginStyle get style => + Theme.of(context).extensionOrNull() ?? + BulletedListPluginStyle.light; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + + Widget get icon => style.icon( + widget.editorState, + widget.textNode, + ); + @override Widget buildWithSingle(BuildContext context) { return Padding( @@ -71,12 +87,9 @@ class _BulletedListTextNodeWidgetState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FlowySvg( + Container( key: iconKey, - width: iconSize?.width, - height: iconSize?.height, - padding: iconPadding, - name: 'point', + child: icon, ), Flexible( child: FlowyRichText( @@ -86,7 +99,7 @@ class _BulletedListTextNodeWidgetState extends State textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, editorState: widget.editorState, ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart index de12388937..a2e0aa5d32 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/checkbox_text.dart @@ -1,10 +1,10 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/commands/text/text_commands.dart'; -import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; import 'package:flutter/material.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class CheckboxNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -39,11 +39,7 @@ class CheckboxNodeWidget extends BuiltInTextWidget { } class _CheckboxNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override final iconKey = GlobalKey(); @@ -58,6 +54,25 @@ class _CheckboxNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + CheckboxPluginStyle get style => + Theme.of(context).extensionOrNull() ?? + CheckboxPluginStyle.light; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + + Widget get icon => style.icon( + widget.editorState, + widget.textNode, + ); + @override Widget buildWithSingle(BuildContext context) { final check = widget.textNode.attributes.check; @@ -68,12 +83,7 @@ class _CheckboxNodeWidgetState extends State children: [ GestureDetector( key: iconKey, - child: FlowySvg( - width: iconSize?.width, - height: iconSize?.height, - padding: iconPadding, - name: check ? 'check' : 'uncheck', - ), + child: icon, onTap: () async { await widget.editorState.formatTextToCheckbox( widget.editorState, @@ -86,7 +96,7 @@ class _CheckboxNodeWidgetState extends State child: FlowyRichText( key: _richTextKey, placeholderText: 'To-do', - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart index c49122dea4..8d96d143cf 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/flowy_rich_text.dart @@ -26,7 +26,7 @@ class FlowyRichText extends StatefulWidget { const FlowyRichText({ Key? key, this.cursorHeight, - this.cursorWidth = 1.0, + this.cursorWidth = 1.5, this.lineHeight = 1.0, this.textSpanDecorator, this.placeholderText = ' ', @@ -55,7 +55,7 @@ class _FlowyRichTextState extends State with SelectableMixin { RenderParagraph get _renderParagraph => _textKey.currentContext?.findRenderObject() as RenderParagraph; - RenderParagraph get _placeholderRenderParagraph => + RenderParagraph? get _placeholderRenderParagraph => _placeholderTextKey.currentContext?.findRenderObject() as RenderParagraph; @override @@ -79,7 +79,7 @@ class _FlowyRichTextState extends State with SelectableMixin { @override Position end() => Position( - path: widget.textNode.path, offset: widget.textNode.delta.length); + path: widget.textNode.path, offset: widget.textNode.toPlainText().length); @override Rect? getCursorRectInPosition(Position position) { @@ -90,12 +90,13 @@ class _FlowyRichTextState extends State with SelectableMixin { _renderParagraph.getOffsetForCaret(textPosition, Rect.zero); if (cursorHeight == null) { cursorHeight = - _placeholderRenderParagraph.getFullHeightForCaret(textPosition); - cursorOffset = _placeholderRenderParagraph.getOffsetForCaret( - textPosition, Rect.zero); + _placeholderRenderParagraph?.getFullHeightForCaret(textPosition); + cursorOffset = _placeholderRenderParagraph?.getOffsetForCaret( + textPosition, Rect.zero) ?? + Offset.zero; } final rect = Rect.fromLTWH( - cursorOffset.dx - (widget.cursorWidth / 2), + cursorOffset.dx - (widget.cursorWidth / 2.0), cursorOffset.dy, widget.cursorWidth, widget.cursorHeight ?? cursorHeight ?? 16.0, @@ -201,12 +202,13 @@ class _FlowyRichTextState extends State with SelectableMixin { } TextSpan get _placeholderTextSpan { - final style = widget.editorState.editorStyle.textStyle; + final placeholderTextStyle = + widget.editorState.editorStyle.placeholderTextStyle; return TextSpan( children: [ TextSpan( text: widget.placeholderText, - style: style.defaultPlaceholderTextStyle, + style: placeholderTextStyle, ), ], ); @@ -215,10 +217,10 @@ class _FlowyRichTextState extends State with SelectableMixin { TextSpan get _textSpan { var offset = 0; List textSpans = []; - final style = widget.editorState.editorStyle.textStyle; + final style = widget.editorState.editorStyle; final textInserts = widget.textNode.delta.whereType(); for (final textInsert in textInserts) { - var textStyle = style.defaultTextStyle; + var textStyle = style.textStyle!; GestureRecognizer? recognizer; final attributes = textInsert.attributes; if (attributes != null) { @@ -297,6 +299,8 @@ class _FlowyRichTextState extends State with SelectableMixin { timer = Timer(const Duration(milliseconds: 200), () { tapCount = 0; + widget.editorState.service.selectionService + .updateSelection(selection); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { showLinkMenu( context, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart index 5b6c75cc6a..f8f5bd0f92 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/heading_text.dart @@ -4,10 +4,12 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart'; import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; +import 'package:appflowy_editor/src/render/style/plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class HeadingTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -43,7 +45,7 @@ class HeadingTextNodeWidget extends BuiltInTextWidget { // customize class _HeadingTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override GlobalKey? get iconKey => null; @@ -58,6 +60,20 @@ class _HeadingTextNodeWidgetState extends State return padding.topLeft; } + HeadingPluginStyle get style => + Theme.of(context).extensionOrNull() ?? + HeadingPluginStyle.light; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + @override Widget build(BuildContext context) { return Padding( @@ -68,7 +84,7 @@ class _HeadingTextNodeWidgetState extends State placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, textNode: widget.textNode, editorState: widget.editorState, ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart index 6ce4bd0fee..60698d6aad 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/number_list_text.dart @@ -4,10 +4,12 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart'; import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; +import 'package:appflowy_editor/src/render/style/plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class NumberListTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -43,7 +45,7 @@ class NumberListTextNodeWidget extends BuiltInTextWidget { } class _NumberListTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override final iconKey = GlobalKey(); @@ -58,6 +60,25 @@ class _NumberListTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + NumberListPluginStyle get style => + Theme.of(context).extensionOrNull() ?? + NumberListPluginStyle.light; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + + Widget get icon => style.icon( + widget.editorState, + widget.textNode, + ); + @override Widget build(BuildContext context) { return Padding( @@ -67,12 +88,7 @@ class _NumberListTextNodeWidgetState extends State children: [ Container( key: iconKey, - padding: iconPadding, - child: Text( - '${widget.textNode.attributes.number.toString()}.', - // FIXME: customize - style: const TextStyle(fontSize: 16.0, color: Colors.black), - ), + child: icon, ), Flexible( child: FlowyRichText( @@ -80,7 +96,7 @@ class _NumberListTextNodeWidgetState extends State placeholderText: 'List', textNode: widget.textNode, editorState: widget.editorState, - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), textSpanDecorator: (textSpan) => diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart index b68fc38923..370d328d1e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/quoted_text.dart @@ -1,13 +1,14 @@ import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart'; import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; +import 'package:appflowy_editor/src/render/style/plugin_styles.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; +import 'package:appflowy_editor/src/extensions/theme_extension.dart'; class QuotedTextNodeWidgetBuilder extends NodeWidgetBuilder { @override @@ -44,7 +45,7 @@ class QuotedTextNodeWidget extends BuiltInTextWidget { // customize class _QuotedTextNodeWidgetState extends State - with SelectableMixin, DefaultSelectable, BuiltInStyleMixin { + with SelectableMixin, DefaultSelectable { @override final iconKey = GlobalKey(); @@ -59,6 +60,25 @@ class _QuotedTextNodeWidgetState extends State return super.baseOffset.translate(0, padding.top); } + QuotedTextPluginStyle get style => + Theme.of(context).extensionOrNull() ?? + QuotedTextPluginStyle.light; + + EdgeInsets get padding => style.padding( + widget.editorState, + widget.textNode, + ); + + TextStyle get textStyle => style.textStyle( + widget.editorState, + widget.textNode, + ); + + Widget get icon => style.icon( + widget.editorState, + widget.textNode, + ); + @override Widget build(BuildContext context) { return Padding( @@ -67,11 +87,9 @@ class _QuotedTextNodeWidgetState extends State child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - FlowySvg( + Container( key: iconKey, - width: iconSize?.width, - padding: iconPadding, - name: 'quote', + child: icon, ), Flexible( child: FlowyRichText( @@ -82,7 +100,7 @@ class _QuotedTextNodeWidgetState extends State textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, editorState: widget.editorState, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart index a28270bf7c..f48714045b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/rich_text/rich_text.dart @@ -4,6 +4,7 @@ import 'package:appflowy_editor/src/render/rich_text/built_in_text_widget.dart'; import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart'; import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart'; import 'package:appflowy_editor/src/render/selection/selectable.dart'; +import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:appflowy_editor/src/service/render_plugin_service.dart'; import 'package:flutter/material.dart'; import 'package:appflowy_editor/src/extensions/text_style_extension.dart'; @@ -43,11 +44,7 @@ class RichTextNodeWidget extends BuiltInTextWidget { // customize class _RichTextNodeWidgetState extends State - with - SelectableMixin, - DefaultSelectable, - BuiltInStyleMixin, - BuiltInTextWidgetMixin { + with SelectableMixin, DefaultSelectable, BuiltInTextWidgetMixin { @override GlobalKey? get iconKey => null; @@ -59,20 +56,26 @@ class _RichTextNodeWidgetState extends State @override Offset get baseOffset { - return padding.topLeft; + return textPadding.topLeft; } + EditorStyle get style => widget.editorState.editorStyle; + + EdgeInsets get textPadding => style.textPadding!; + + TextStyle get textStyle => style.textStyle!; + @override Widget buildWithSingle(BuildContext context) { return Padding( - padding: padding, + padding: textPadding, child: FlowyRichText( key: _richTextKey, textNode: widget.textNode, textSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle(textStyle), - lineHeight: widget.editorState.editorStyle.textStyle.lineHeight, + lineHeight: widget.editorState.editorStyle.lineHeight, editorState: widget.editorState, ), ); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart index a4322c59fe..912d9447ff 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_item_widget.dart @@ -3,7 +3,7 @@ import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; import 'package:flutter/material.dart'; -class SelectionMenuItemWidget extends StatelessWidget { +class SelectionMenuItemWidget extends StatefulWidget { const SelectionMenuItemWidget({ Key? key, required this.editorState, @@ -11,7 +11,6 @@ class SelectionMenuItemWidget extends StatelessWidget { required this.item, required this.isSelected, this.width = 140.0, - this.selectedColor = const Color(0xFFE0F8FF), }) : super(key: key); final EditorState editorState; @@ -19,33 +18,52 @@ class SelectionMenuItemWidget extends StatelessWidget { final SelectionMenuItem item; final double width; final bool isSelected; - final Color selectedColor; + + @override + State createState() => + _SelectionMenuItemWidgetState(); +} + +class _SelectionMenuItemWidgetState extends State { + var _onHover = false; @override Widget build(BuildContext context) { + final editorStyle = widget.editorState.editorStyle; return Container( padding: const EdgeInsets.fromLTRB(8.0, 5.0, 8.0, 5.0), child: SizedBox( - width: width, + width: widget.width, child: TextButton.icon( - icon: item.icon, + icon: widget.item + .icon(widget.editorState, widget.isSelected || _onHover), style: ButtonStyle( alignment: Alignment.centerLeft, - overlayColor: MaterialStateProperty.all(selectedColor), - backgroundColor: isSelected - ? MaterialStateProperty.all(selectedColor) + overlayColor: MaterialStateProperty.all( + editorStyle.selectionMenuItemSelectedColor), + backgroundColor: widget.isSelected + ? MaterialStateProperty.all( + editorStyle.selectionMenuItemSelectedColor) : MaterialStateProperty.all(Colors.transparent), ), label: Text( - item.name(), + widget.item.name(), textAlign: TextAlign.left, - style: const TextStyle( - color: Colors.black, - fontSize: 14.0, + style: TextStyle( + color: (widget.isSelected || _onHover) + ? editorStyle.selectionMenuItemSelectedTextColor + : editorStyle.selectionMenuItemTextColor, + fontSize: 12.0, ), ), onPressed: () { - item.handler(editorState, menuService, context); + widget.item + .handler(widget.editorState, widget.menuService, context); + }, + onHover: (value) { + setState(() { + _onHover = value; + }); }, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart index 614546ef99..402e7cee84 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_service.dart @@ -59,15 +59,30 @@ class SelectionMenu implements SelectionMenuService { // Workaround: We can customize the padding through the [EditorStyle], // but the coordinates of overlay are not properly converted currently. // Just subtract the padding here as a result. - final baseOffset = + const menuHeight = 200.0; + const menuOffset = Offset(0, 10); + final editorOffset = editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; - final offset = - selectionRects.first.bottomRight + const Offset(10, 10) - baseOffset; + final editorHeight = editorState.renderBox!.size.height; + + // show below defualt + var showBelow = true; + final bottomRight = selectionRects.first.bottomRight; + final topRight = selectionRects.first.topRight; + var offset = bottomRight + menuOffset; + // overflow + if (offset.dy + menuHeight >= editorOffset.dy + editorHeight) { + // show above + offset = topRight - menuOffset; + showBelow = false; + } _topLeft = offset; _selectionMenuEntry = OverlayEntry(builder: (context) { return Positioned( - top: offset.dy, + top: showBelow ? offset.dy : null, + bottom: + showBelow ? null : MediaQuery.of(context).size.height - offset.dy, left: offset.dx, child: SelectionMenuWidget( items: [ @@ -125,7 +140,8 @@ List get defaultSelectionMenuItems => final List _defaultSelectionMenuItems = [ SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.text, - icon: _selectionMenuIcon('text'), + icon: (editorState, onSelected) => + _selectionMenuIcon('text', editorState, onSelected), keywords: ['text'], handler: (editorState, _, __) { insertTextNodeAfterSelection(editorState, {}); @@ -133,7 +149,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading1, - icon: _selectionMenuIcon('h1'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h1', editorState, onSelected), keywords: ['heading 1, h1'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h1); @@ -141,7 +158,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading2, - icon: _selectionMenuIcon('h2'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h2', editorState, onSelected), keywords: ['heading 2, h2'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h2); @@ -149,7 +167,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.heading3, - icon: _selectionMenuIcon('h3'), + icon: (editorState, onSelected) => + _selectionMenuIcon('h3', editorState, onSelected), keywords: ['heading 3, h3'], handler: (editorState, _, __) { insertHeadingAfterSelection(editorState, BuiltInAttributeKey.h3); @@ -157,13 +176,15 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.image, - icon: _selectionMenuIcon('image'), + icon: (editorState, onSelected) => + _selectionMenuIcon('image', editorState, onSelected), keywords: ['image'], handler: showImageUploadMenu, ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.bulletedList, - icon: _selectionMenuIcon('bulleted_list'), + icon: (editorState, onSelected) => + _selectionMenuIcon('bulleted_list', editorState, onSelected), keywords: ['bulleted list', 'list', 'unordered list'], handler: (editorState, _, __) { insertBulletedListAfterSelection(editorState); @@ -171,7 +192,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.numberedList, - icon: _selectionMenuIcon('number'), + icon: (editorState, onSelected) => + _selectionMenuIcon('number', editorState, onSelected), keywords: ['numbered list', 'list', 'ordered list'], handler: (editorState, _, __) { insertNumberedListAfterSelection(editorState); @@ -179,7 +201,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.checkbox, - icon: _selectionMenuIcon('checkbox'), + icon: (editorState, onSelected) => + _selectionMenuIcon('checkbox', editorState, onSelected), keywords: ['todo list', 'list', 'checkbox list'], handler: (editorState, _, __) { insertCheckboxAfterSelection(editorState); @@ -187,7 +210,8 @@ final List _defaultSelectionMenuItems = [ ), SelectionMenuItem( name: () => AppFlowyEditorLocalizations.current.quote, - icon: _selectionMenuIcon('quote'), + icon: (editorState, onSelected) => + _selectionMenuIcon('quote', editorState, onSelected), keywords: ['quote', 'refer'], handler: (editorState, _, __) { insertQuoteAfterSelection(editorState); @@ -195,10 +219,13 @@ final List _defaultSelectionMenuItems = [ ), ]; -Widget _selectionMenuIcon(String name) { +Widget _selectionMenuIcon( + String name, EditorState editorState, bool onSelected) { return FlowySvg( name: 'selection_menu/$name', - color: Colors.black, + color: onSelected + ? editorState.editorStyle.selectionMenuItemSelectedIconColor + : editorState.editorStyle.selectionMenuItemIconColor, width: 18.0, height: 18.0, ); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart index 2f64e8a4a6..2007c172f5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/selection_menu/selection_menu_widget.dart @@ -29,7 +29,7 @@ class SelectionMenuItem { } final String Function() name; - final Widget icon; + final Widget Function(EditorState editorState, bool onSelected) icon; /// Customizes keywords for item. /// @@ -142,7 +142,7 @@ class _SelectionMenuWidgetState extends State { onKey: _onKey, child: Container( decoration: BoxDecoration( - color: Colors.white, + color: widget.editorState.editorStyle.selectionMenuBackgroundColor, boxShadow: [ BoxShadow( blurRadius: 5, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart index a8cc9eb638..93305bb31e 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/editor_style.dart @@ -1,202 +1,81 @@ import 'package:flutter/material.dart'; -import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/extensions/attributes_extension.dart'; +Iterable> get lightEditorStyleExtension => [ + EditorStyle.light, + ]; -typedef PluginStyler = Object Function(EditorState editorState, Node node); -typedef PluginStyle = Map; +Iterable> get darkEditorStyleExtension => [ + EditorStyle.dark, + ]; + +class EditorStyle extends ThemeExtension { + // Editor styles + final EdgeInsets? padding; + final Color? backgroundColor; + final Color? cursorColor; + final Color? selectionColor; + + // Selection menu styles + final Color? selectionMenuBackgroundColor; + final Color? selectionMenuItemTextColor; + final Color? selectionMenuItemIconColor; + final Color? selectionMenuItemSelectedTextColor; + final Color? selectionMenuItemSelectedIconColor; + final Color? selectionMenuItemSelectedColor; + + // Text styles + final EdgeInsets? textPadding; + final TextStyle? textStyle; + final TextStyle? placeholderTextStyle; + final double lineHeight; + + // Rich text styles + final TextStyle? bold; + final TextStyle? italic; + final TextStyle? underline; + final TextStyle? strikethrough; + final TextStyle? href; + final TextStyle? code; + final String? highlightColorHex; -/// Editor style configuration -class EditorStyle { EditorStyle({ required this.padding, - required this.textStyle, + required this.backgroundColor, required this.cursorColor, required this.selectionColor, - Map pluginStyles = const {}, - }) { - _pluginStyles.addAll(pluginStyles); - } - - EditorStyle.defaultStyle() - : padding = const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), - textStyle = BuiltInTextStyle.builtIn(), - cursorColor = const Color(0xFF00BCF0), - selectionColor = const Color.fromARGB(53, 111, 201, 231); - - /// The margin of the document context from the editor. - final EdgeInsets padding; - final BuiltInTextStyle textStyle; - final Color cursorColor; - final Color selectionColor; - - final Map _pluginStyles = Map.from(builtInTextStylers); - - Object? style(EditorState editorState, Node node, String key) { - final styler = _pluginStyles[node.id]?[key]; - if (styler != null) { - return styler(editorState, node); - } - return null; - } - - EditorStyle copyWith({ - EdgeInsets? padding, - BuiltInTextStyle? textStyle, - Color? cursorColor, - Color? selectionColor, - Map? pluginStyles, - }) { - return EditorStyle( - padding: padding ?? this.padding, - textStyle: textStyle ?? this.textStyle, - cursorColor: cursorColor ?? this.cursorColor, - selectionColor: selectionColor ?? this.selectionColor, - pluginStyles: pluginStyles ?? {}, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is EditorStyle && - other.padding == padding && - other.textStyle == textStyle && - other.cursorColor == cursorColor && - other.selectionColor == selectionColor; - } - - @override - int get hashCode { - return padding.hashCode ^ - textStyle.hashCode ^ - cursorColor.hashCode ^ - selectionColor.hashCode; - } -} - -PluginStyle get builtInPluginStyle => Map.from({ - 'padding': (_, __) => const EdgeInsets.symmetric(vertical: 8.0), - 'textStyle': (_, __) => const TextStyle(), - 'iconSize': (_, __) => const Size.square(20.0), - 'iconPadding': (_, __) => const EdgeInsets.only(right: 5.0), - }); - -Map builtInTextStylers = { - 'text': builtInPluginStyle, - 'text/checkbox': builtInPluginStyle - ..update( - 'textStyle', - (_) => (EditorState editorState, Node node) { - if (node is TextNode && node.attributes.check == true) { - return const TextStyle( - color: Colors.grey, - decoration: TextDecoration.lineThrough, - ); - } - return const TextStyle(); - }, - ), - 'text/heading': builtInPluginStyle - ..update( - 'textStyle', - (_) => (EditorState editorState, Node node) { - final headingToFontSize = { - 'h1': 32.0, - 'h2': 28.0, - 'h3': 24.0, - 'h4': 18.0, - 'h5': 18.0, - 'h6': 18.0, - }; - final fontSize = headingToFontSize[node.attributes.heading] ?? 18.0; - return TextStyle(fontSize: fontSize, fontWeight: FontWeight.bold); - }, - ), - 'text/bulleted-list': builtInPluginStyle, - 'text/number-list': builtInPluginStyle - ..update( - 'iconPadding', - (_) => (EditorState editorState, Node node) { - return const EdgeInsets.only(left: 5.0, right: 5.0); - }, - ), - 'text/quote': builtInPluginStyle, - 'image': builtInPluginStyle, -}; - -class BuiltInTextStyle { - const BuiltInTextStyle({ - required this.defaultTextStyle, - required this.defaultPlaceholderTextStyle, + required this.selectionMenuBackgroundColor, + required this.selectionMenuItemTextColor, + required this.selectionMenuItemIconColor, + required this.selectionMenuItemSelectedTextColor, + required this.selectionMenuItemSelectedIconColor, + required this.selectionMenuItemSelectedColor, + required this.textPadding, + required this.textStyle, + required this.placeholderTextStyle, required this.bold, required this.italic, required this.underline, required this.strikethrough, required this.href, required this.code, - this.highlightColorHex = '0x6000BCF0', - this.lineHeight = 1.5, + required this.highlightColorHex, + required this.lineHeight, }); - final TextStyle defaultTextStyle; - final TextStyle defaultPlaceholderTextStyle; - final TextStyle bold; - final TextStyle italic; - final TextStyle underline; - final TextStyle strikethrough; - final TextStyle href; - final TextStyle code; - final String highlightColorHex; - final double lineHeight; - - BuiltInTextStyle.builtIn() - : defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.black), - defaultPlaceholderTextStyle = - const TextStyle(fontSize: 16.0, color: Colors.grey), - bold = const TextStyle(fontWeight: FontWeight.bold), - italic = const TextStyle(fontStyle: FontStyle.italic), - underline = const TextStyle(decoration: TextDecoration.underline), - strikethrough = const TextStyle(decoration: TextDecoration.lineThrough), - href = const TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - code = const TextStyle( - fontFamily: 'monospace', - color: Color(0xFF00BCF0), - backgroundColor: Color(0xFFE0F8FF), - ), - highlightColorHex = '0x6000BCF0', - lineHeight = 1.5; - - BuiltInTextStyle.builtInDarkMode() - : defaultTextStyle = const TextStyle(fontSize: 16.0, color: Colors.white), - defaultPlaceholderTextStyle = TextStyle( - fontSize: 16.0, - color: Colors.white.withOpacity(0.3), - ), - bold = const TextStyle(fontWeight: FontWeight.bold), - italic = const TextStyle(fontStyle: FontStyle.italic), - underline = const TextStyle(decoration: TextDecoration.underline), - strikethrough = const TextStyle(decoration: TextDecoration.lineThrough), - href = const TextStyle( - color: Colors.blue, - decoration: TextDecoration.underline, - ), - code = const TextStyle( - fontFamily: 'monospace', - color: Color(0xFF00BCF0), - backgroundColor: Color(0xFFE0F8FF), - ), - highlightColorHex = '0x6000BCF0', - lineHeight = 1.5; - - BuiltInTextStyle copyWith({ - TextStyle? defaultTextStyle, - TextStyle? defaultPlaceholderTextStyle, + @override + EditorStyle copyWith({ + EdgeInsets? padding, + Color? backgroundColor, + Color? cursorColor, + Color? selectionColor, + Color? selectionMenuBackgroundColor, + Color? selectionMenuItemTextColor, + Color? selectionMenuItemIconColor, + Color? selectionMenuItemSelectedTextColor, + Color? selectionMenuItemSelectedIconColor, + Color? selectionMenuItemSelectedColor, + TextStyle? textStyle, + TextStyle? placeholderTextStyle, TextStyle? bold, TextStyle? italic, TextStyle? underline, @@ -206,10 +85,26 @@ class BuiltInTextStyle { String? highlightColorHex, double? lineHeight, }) { - return BuiltInTextStyle( - defaultTextStyle: defaultTextStyle ?? this.defaultTextStyle, - defaultPlaceholderTextStyle: - defaultPlaceholderTextStyle ?? this.defaultPlaceholderTextStyle, + return EditorStyle( + padding: padding ?? this.padding, + backgroundColor: backgroundColor ?? this.backgroundColor, + cursorColor: cursorColor ?? this.cursorColor, + selectionColor: selectionColor ?? this.selectionColor, + selectionMenuBackgroundColor: + selectionMenuBackgroundColor ?? this.selectionMenuBackgroundColor, + selectionMenuItemTextColor: + selectionMenuItemTextColor ?? this.selectionMenuItemTextColor, + selectionMenuItemIconColor: + selectionMenuItemIconColor ?? this.selectionMenuItemIconColor, + selectionMenuItemSelectedTextColor: selectionMenuItemSelectedTextColor ?? + this.selectionMenuItemSelectedTextColor, + selectionMenuItemSelectedIconColor: selectionMenuItemSelectedIconColor ?? + this.selectionMenuItemSelectedIconColor, + selectionMenuItemSelectedColor: + selectionMenuItemSelectedColor ?? this.selectionMenuItemSelectedColor, + textPadding: textPadding ?? textPadding, + textStyle: textStyle ?? this.textStyle, + placeholderTextStyle: placeholderTextStyle ?? this.placeholderTextStyle, bold: bold ?? this.bold, italic: italic ?? this.italic, underline: underline ?? this.underline, @@ -222,33 +117,90 @@ class BuiltInTextStyle { } @override - bool operator ==(Object other) { - if (identical(this, other)) return true; - - return other is BuiltInTextStyle && - other.defaultTextStyle == defaultTextStyle && - other.defaultPlaceholderTextStyle == defaultPlaceholderTextStyle && - other.bold == bold && - other.italic == italic && - other.underline == underline && - other.strikethrough == strikethrough && - other.href == href && - other.code == code && - other.highlightColorHex == highlightColorHex && - other.lineHeight == lineHeight; + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other == null || other is! EditorStyle) { + return this; + } + return EditorStyle( + padding: EdgeInsets.lerp(padding, other.padding, t), + backgroundColor: Color.lerp(backgroundColor, other.backgroundColor, t), + cursorColor: Color.lerp(cursorColor, other.cursorColor, t), + textPadding: EdgeInsets.lerp(textPadding, other.textPadding, t), + selectionColor: Color.lerp(selectionColor, other.selectionColor, t), + selectionMenuBackgroundColor: Color.lerp( + selectionMenuBackgroundColor, other.selectionMenuBackgroundColor, t), + selectionMenuItemTextColor: Color.lerp( + selectionMenuItemTextColor, other.selectionMenuItemTextColor, t), + selectionMenuItemIconColor: Color.lerp( + selectionMenuItemIconColor, other.selectionMenuItemIconColor, t), + selectionMenuItemSelectedTextColor: Color.lerp( + selectionMenuItemSelectedTextColor, + other.selectionMenuItemSelectedTextColor, + t), + selectionMenuItemSelectedIconColor: Color.lerp( + selectionMenuItemSelectedIconColor, + other.selectionMenuItemSelectedIconColor, + t), + selectionMenuItemSelectedColor: Color.lerp(selectionMenuItemSelectedColor, + other.selectionMenuItemSelectedColor, t), + textStyle: TextStyle.lerp(textStyle, other.textStyle, t), + placeholderTextStyle: + TextStyle.lerp(placeholderTextStyle, other.placeholderTextStyle, t), + bold: TextStyle.lerp(bold, other.bold, t), + italic: TextStyle.lerp(italic, other.italic, t), + underline: TextStyle.lerp(underline, other.underline, t), + strikethrough: TextStyle.lerp(strikethrough, other.strikethrough, t), + href: TextStyle.lerp(href, other.href, t), + code: TextStyle.lerp(code, other.code, t), + highlightColorHex: highlightColorHex, + lineHeight: lineHeight, + ); } - @override - int get hashCode { - return defaultTextStyle.hashCode ^ - defaultPlaceholderTextStyle.hashCode ^ - bold.hashCode ^ - italic.hashCode ^ - underline.hashCode ^ - strikethrough.hashCode ^ - href.hashCode ^ - code.hashCode ^ - highlightColorHex.hashCode ^ - lineHeight.hashCode; - } + static final light = EditorStyle( + padding: const EdgeInsets.fromLTRB(200.0, 0.0, 200.0, 0.0), + backgroundColor: Colors.white, + cursorColor: const Color(0xFF00BCF0), + selectionColor: const Color.fromARGB(53, 111, 201, 231), + selectionMenuBackgroundColor: const Color(0xFFFFFFFF), + selectionMenuItemTextColor: const Color(0xFF333333), + selectionMenuItemIconColor: const Color(0xFF333333), + selectionMenuItemSelectedTextColor: const Color(0xFF333333), + selectionMenuItemSelectedIconColor: const Color(0xFF333333), + selectionMenuItemSelectedColor: const Color(0xFFE0F8FF), + textPadding: const EdgeInsets.symmetric(vertical: 8.0), + textStyle: const TextStyle(fontSize: 16.0, color: Colors.black), + placeholderTextStyle: const TextStyle(fontSize: 16.0, color: Colors.grey), + bold: const TextStyle(fontWeight: FontWeight.bold), + italic: const TextStyle(fontStyle: FontStyle.italic), + underline: const TextStyle(decoration: TextDecoration.underline), + strikethrough: const TextStyle(decoration: TextDecoration.lineThrough), + href: const TextStyle( + color: Colors.blue, + decoration: TextDecoration.underline, + ), + code: const TextStyle( + fontFamily: 'monospace', + color: Color(0xFF00BCF0), + backgroundColor: Color(0xFFE0F8FF), + ), + highlightColorHex: '0x6000BCF0', + lineHeight: 1.5, + ); + + static final dark = light.copyWith( + backgroundColor: Colors.black, + textStyle: const TextStyle(fontSize: 16.0, color: Colors.white), + placeholderTextStyle: TextStyle( + fontSize: 16.0, + color: Colors.white.withOpacity(0.3), + ), + selectionMenuBackgroundColor: const Color(0xFF282E3A), + selectionMenuItemTextColor: const Color(0xFFBBC3CD), + selectionMenuItemIconColor: const Color(0xFFBBC3CD), + selectionMenuItemSelectedTextColor: const Color(0xFF131720), + selectionMenuItemSelectedIconColor: const Color(0xFF131720), + selectionMenuItemSelectedColor: const Color(0xFF00BCF0), + ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_styles.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_styles.dart new file mode 100644 index 0000000000..44fabea573 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/style/plugin_styles.dart @@ -0,0 +1,333 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/infra/flowy_svg.dart'; +import 'package:flutter/material.dart'; + +Iterable> get lightPlguinStyleExtension => [ + HeadingPluginStyle.light, + CheckboxPluginStyle.light, + NumberListPluginStyle.light, + QuotedTextPluginStyle.light, + ]; + +Iterable> get darkPlguinStyleExtension => [ + HeadingPluginStyle.dark, + CheckboxPluginStyle.dark, + NumberListPluginStyle.dark, + QuotedTextPluginStyle.dark, + BulletedListPluginStyle.dark, + ]; + +typedef TextStyleCustomizer = TextStyle Function( + EditorState editorState, TextNode textNode); +typedef PaddingCustomizer = EdgeInsets Function( + EditorState editorState, TextNode textNode); +typedef IconCustomizer = Widget Function( + EditorState editorState, TextNode textNode); + +class HeadingPluginStyle extends ThemeExtension { + const HeadingPluginStyle({ + required this.textStyle, + required this.padding, + }); + + final TextStyleCustomizer textStyle; + final PaddingCustomizer padding; + + @override + HeadingPluginStyle copyWith({ + TextStyleCustomizer? textStyle, + PaddingCustomizer? padding, + }) { + return HeadingPluginStyle( + textStyle: textStyle ?? this.textStyle, + padding: padding ?? this.padding, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! HeadingPluginStyle) { + return this; + } + return HeadingPluginStyle( + textStyle: other.textStyle, + padding: other.padding, + ); + } + + static final light = HeadingPluginStyle( + padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0), + textStyle: (editorState, textNode) { + final headingToFontSize = { + 'h1': 32.0, + 'h2': 28.0, + 'h3': 24.0, + 'h4': 18.0, + 'h5': 18.0, + 'h6': 18.0, + }; + final fontSize = headingToFontSize[textNode.attributes.heading] ?? 18.0; + return TextStyle( + fontSize: fontSize, + fontWeight: FontWeight.bold, + ); + }, + ); + + static final dark = light; +} + +class CheckboxPluginStyle extends ThemeExtension { + const CheckboxPluginStyle({ + required this.textStyle, + required this.padding, + required this.icon, + }); + + final TextStyleCustomizer textStyle; + final PaddingCustomizer padding; + final IconCustomizer icon; + + @override + CheckboxPluginStyle copyWith({ + TextStyleCustomizer? textStyle, + PaddingCustomizer? padding, + IconCustomizer? icon, + }) { + return CheckboxPluginStyle( + textStyle: textStyle ?? this.textStyle, + padding: padding ?? this.padding, + icon: icon ?? this.icon, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! CheckboxPluginStyle) { + return this; + } + return CheckboxPluginStyle( + textStyle: other.textStyle, + padding: other.padding, + icon: other.icon, + ); + } + + static final light = CheckboxPluginStyle( + padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0), + textStyle: (editorState, textNode) { + final isCheck = textNode.attributes.check; + return TextStyle( + decoration: isCheck ? TextDecoration.lineThrough : null, + color: isCheck ? Colors.grey.shade400 : null, + ); + }, + icon: (editorState, textNode) { + final isCheck = textNode.attributes.check; + const iconSize = Size.square(20.0); + const iconPadding = EdgeInsets.only(right: 5.0); + return FlowySvg( + width: iconSize.width, + height: iconSize.height, + padding: iconPadding, + name: isCheck ? 'check' : 'uncheck', + ); + }, + ); + + static final dark = light; +} + +class BulletedListPluginStyle extends ThemeExtension { + const BulletedListPluginStyle({ + required this.textStyle, + required this.padding, + required this.icon, + }); + + final TextStyleCustomizer textStyle; + final PaddingCustomizer padding; + final IconCustomizer icon; + + @override + BulletedListPluginStyle copyWith({ + TextStyleCustomizer? textStyle, + PaddingCustomizer? padding, + IconCustomizer? icon, + }) { + return BulletedListPluginStyle( + textStyle: textStyle ?? this.textStyle, + padding: padding ?? this.padding, + icon: icon ?? this.icon, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! BulletedListPluginStyle) { + return this; + } + return BulletedListPluginStyle( + textStyle: other.textStyle, + padding: other.padding, + icon: other.icon, + ); + } + + static final light = BulletedListPluginStyle( + padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0), + textStyle: (_, __) => const TextStyle(), + icon: (_, __) { + const iconSize = Size.square(20.0); + const iconPadding = EdgeInsets.only(right: 5.0); + return FlowySvg( + width: iconSize.width, + height: iconSize.height, + padding: iconPadding, + color: Colors.black, + name: 'point', + ); + }, + ); + + static final dark = light.copyWith(icon: (_, __) { + const iconSize = Size.square(20.0); + const iconPadding = EdgeInsets.only(right: 5.0); + return FlowySvg( + width: iconSize.width, + height: iconSize.height, + padding: iconPadding, + color: Colors.white, + name: 'point', + ); + }); +} + +class NumberListPluginStyle extends ThemeExtension { + const NumberListPluginStyle({ + required this.textStyle, + required this.padding, + required this.icon, + }); + + final TextStyleCustomizer textStyle; + final PaddingCustomizer padding; + final IconCustomizer icon; + + @override + NumberListPluginStyle copyWith({ + TextStyleCustomizer? textStyle, + PaddingCustomizer? padding, + IconCustomizer? icon, + }) { + return NumberListPluginStyle( + textStyle: textStyle ?? this.textStyle, + padding: padding ?? this.padding, + icon: icon ?? this.icon, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, + double t, + ) { + if (other is! NumberListPluginStyle) { + return this; + } + return NumberListPluginStyle( + textStyle: other.textStyle, + padding: other.padding, + icon: other.icon, + ); + } + + static final light = NumberListPluginStyle( + padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0), + textStyle: (_, __) => const TextStyle(), + icon: (_, textNode) { + const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0); + return Container( + padding: iconPadding, + child: Text( + '${textNode.attributes.number.toString()}.', + style: const TextStyle( + fontSize: 16, + color: Colors.black, + ), + ), + ); + }, + ); + + static final dark = light.copyWith(icon: (editorState, textNode) { + const iconPadding = EdgeInsets.only(left: 5.0, right: 5.0); + return Container( + padding: iconPadding, + child: Text( + '${textNode.attributes.number.toString()}.', + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + ); + }); +} + +class QuotedTextPluginStyle extends ThemeExtension { + const QuotedTextPluginStyle({ + required this.textStyle, + required this.padding, + required this.icon, + }); + + final TextStyleCustomizer textStyle; + final PaddingCustomizer padding; + final IconCustomizer icon; + + @override + QuotedTextPluginStyle copyWith({ + TextStyleCustomizer? textStyle, + PaddingCustomizer? padding, + IconCustomizer? icon, + }) { + return QuotedTextPluginStyle( + textStyle: textStyle ?? this.textStyle, + padding: padding ?? this.padding, + icon: icon ?? this.icon, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! QuotedTextPluginStyle) { + return this; + } + return QuotedTextPluginStyle( + textStyle: other.textStyle, + padding: other.padding, + icon: other.icon, + ); + } + + static final light = QuotedTextPluginStyle( + padding: (_, __) => const EdgeInsets.symmetric(vertical: 8.0), + textStyle: (_, __) => const TextStyle(), + icon: (_, __) { + const iconSize = Size.square(20.0); + const iconPadding = EdgeInsets.only(right: 5.0); + return FlowySvg( + width: iconSize.width, + padding: iconPadding, + name: 'quote', + ); + }, + ); + + static final dark = light; +} diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart index 359fde0ac0..5ab7f6cc50 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item.dart @@ -1,13 +1,14 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/commands/text/text_commands.dart'; import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart'; +import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/infra/flowy_svg.dart'; import 'package:appflowy_editor/src/render/link_menu/link_menu.dart'; import 'package:appflowy_editor/src/extensions/text_node_extensions.dart'; import 'package:appflowy_editor/src/extensions/editor_state_extensions.dart'; import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:rich_clipboard/rich_clipboard.dart'; typedef ToolbarItemEventHandler = void Function( @@ -206,7 +207,9 @@ List defaultToolbarItems = [ BuiltInAttributeKey.subtype, (value) => value == BuiltInAttributeKey.quote, ), - handler: (editorState, context) => formatQuote(editorState), + handler: (editorState, context) { + formatQuote(editorState); + }, ), ToolbarItem( id: 'appflowy.toolbar.bulleted_list', @@ -256,7 +259,7 @@ List defaultToolbarItems = [ ), handler: (editorState, context) => formatHighlight( editorState, - editorState.editorStyle.textStyle.highlightColorHex, + editorState.editorStyle.highlightColorHex!, ), ), ]; @@ -345,6 +348,7 @@ void showLinkMenu( child: Material( child: LinkMenu( linkText: linkText, + editorState: editorState, onOpenLink: () async { await safeLaunchUrl(linkText); }, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart index 4fefa8eadb..4b6170620b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_item_widget.dart @@ -25,7 +25,8 @@ class ToolbarItemWidget extends StatelessWidget { child: MouseRegion( cursor: SystemMouseCursors.click, child: IconButton( - highlightColor: Colors.yellow, + hoverColor: Colors.transparent, + highlightColor: Colors.transparent, padding: EdgeInsets.zero, icon: item.iconBuilder(isHighlight), iconSize: 28, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart index 18c2cdc0b1..d987b2f87b 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/render/toolbar/toolbar_widget.dart @@ -1,6 +1,7 @@ +import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart'; import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:appflowy_editor/src/editor_state.dart'; @@ -67,6 +68,7 @@ class _ToolbarWidgetState extends State with ToolbarMixin { isHighlight: item.highlightCallback(widget.editorState), onPressed: () { item.handler(widget.editorState, context); + widget.editorState.service.keyboardService?.enable(); }, ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart index 80fceb03d1..92b43abdbb 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/context_menu/context_menu.dart @@ -30,26 +30,43 @@ class ContextMenu extends StatelessWidget { final children = []; for (var i = 0; i < items.length; i++) { for (var j = 0; j < items[i].length; j++) { + var onHover = false; children.add( - Material( - child: InkWell( - hoverColor: const Color(0xFFE0F8FF), - customBorder: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(6), - ), - onTap: () { - items[i][j].onPressed(editorState); - onPressed(); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - items[i][j].name, - textAlign: TextAlign.start, - style: const TextStyle(fontSize: 14), + StatefulBuilder( + builder: (BuildContext context, setState) { + return Material( + color: editorState.editorStyle.selectionMenuBackgroundColor, + child: InkWell( + hoverColor: + editorState.editorStyle.selectionMenuItemSelectedColor, + customBorder: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + onTap: () { + items[i][j].onPressed(editorState); + onPressed(); + }, + onHover: (value) => setState(() { + onHover = value; + }), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + items[i][j].name, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: 14, + color: onHover + ? editorState + .editorStyle.selectionMenuItemSelectedTextColor + : editorState + .editorStyle.selectionMenuItemTextColor, + ), + ), + ), ), - ), - ), + ); + }, ), ); } @@ -67,7 +84,7 @@ class ContextMenu extends StatelessWidget { minWidth: 140, ), decoration: BoxDecoration( - color: Colors.white, + color: editorState.editorStyle.selectionMenuBackgroundColor, boxShadow: [ BoxShadow( blurRadius: 5, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart index 0229d2270f..f0312bdeff 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/default_text_operations/format_rich_text_style.dart @@ -91,6 +91,10 @@ void formatBulletedList(EditorState editorState) { }); } +/// Format the current selection with the given attributes. +/// +/// If the selected nodes are not text nodes, this method will do nothing. +/// If the selected text nodes already contain the style in attributes, this method will remove the existing style. bool formatTextNodes(EditorState editorState, Attributes attributes) { final nodes = editorState.service.selectionService.currentSelectedNodes; final textNodes = nodes.whereType().toList(); @@ -108,7 +112,16 @@ bool formatTextNodes(EditorState editorState, Attributes attributes) { newAttributes[globalStyleKey] = null; } } - newAttributes.addAll(attributes); + + // if an attribute already exists in the node, it should be removed instead + for (final entry in attributes.entries) { + if (textNode.attributes.containsKey(entry.key) && + textNode.attributes[entry.key] == entry.value) { + // attribute is not added to the node new attributes + } else { + newAttributes.addEntries([entry]); + } + } transaction ..updateNode( textNode, diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart index 3d9599383d..9d58eb066c 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/editor_service.dart @@ -1,11 +1,9 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/render/image/image_node_builder.dart'; -import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart'; -import 'package:appflowy_editor/src/render/style/editor_style.dart'; import 'package:appflowy_editor/src/service/shortcut_event/built_in_shortcut_events.dart'; -import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Overlay, OverlayEntry; -import 'package:appflowy_editor/src/editor_state.dart'; import 'package:appflowy_editor/src/render/editor/editor_entry.dart'; import 'package:appflowy_editor/src/render/rich_text/bulleted_list_text.dart'; import 'package:appflowy_editor/src/render/rich_text/checkbox_text.dart'; @@ -13,12 +11,6 @@ import 'package:appflowy_editor/src/render/rich_text/heading_text.dart'; import 'package:appflowy_editor/src/render/rich_text/number_list_text.dart'; import 'package:appflowy_editor/src/render/rich_text/quoted_text.dart'; import 'package:appflowy_editor/src/render/rich_text/rich_text.dart'; -import 'package:appflowy_editor/src/service/input_service.dart'; -import 'package:appflowy_editor/src/service/keyboard_service.dart'; -import 'package:appflowy_editor/src/service/render_plugin_service.dart'; -import 'package:appflowy_editor/src/service/scroll_service.dart'; -import 'package:appflowy_editor/src/service/selection_service.dart'; -import 'package:appflowy_editor/src/service/toolbar_service.dart'; NodeWidgetBuilders defaultBuilders = { 'editor': EditorEntryWidgetBuilder(), @@ -32,15 +24,22 @@ NodeWidgetBuilders defaultBuilders = { }; class AppFlowyEditor extends StatefulWidget { - const AppFlowyEditor({ + AppFlowyEditor({ Key? key, required this.editorState, this.customBuilders = const {}, this.shortcutEvents = const [], this.selectionMenuItems = const [], this.editable = true, - required this.editorStyle, - }) : super(key: key); + this.autoFocus = false, + ThemeData? themeData, + }) : super(key: key) { + this.themeData = themeData ?? + ThemeData.light().copyWith(extensions: [ + ...lightEditorStyleExtension, + ...lightPlguinStyleExtension, + ]); + } final EditorState editorState; @@ -52,10 +51,13 @@ class AppFlowyEditor extends StatefulWidget { final List selectionMenuItems; - final EditorStyle editorStyle; + late final ThemeData themeData; final bool editable; + /// Set the value to true to focus the editor on the start of the document. + final bool autoFocus; + @override State createState() => _AppFlowyEditorState(); } @@ -64,15 +66,26 @@ class _AppFlowyEditorState extends State { Widget? services; EditorState get editorState => widget.editorState; + EditorStyle get editorStyle => + editorState.themeData.extension() ?? EditorStyle.light; @override void initState() { super.initState(); editorState.selectionMenuItems = widget.selectionMenuItems; - editorState.editorStyle = widget.editorStyle; + editorState.themeData = widget.themeData; editorState.service.renderPluginService = _createRenderPlugin(); editorState.editable = widget.editable; + + // auto focus + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (widget.editable && widget.autoFocus) { + editorState.service.selectionService.updateSelection( + Selection.single(path: [0], startOffset: 0), + ); + } + }); } @override @@ -84,7 +97,7 @@ class _AppFlowyEditorState extends State { editorState.service.renderPluginService = _createRenderPlugin(); } - editorState.editorStyle = widget.editorStyle; + editorState.themeData = widget.themeData; editorState.editable = widget.editable; services = null; } @@ -101,38 +114,42 @@ class _AppFlowyEditorState extends State { ); } - AppFlowyScroll _buildServices(BuildContext context) { - return AppFlowyScroll( - key: editorState.service.scrollServiceKey, - child: Padding( - padding: widget.editorStyle.padding, - child: AppFlowySelection( - key: editorState.service.selectionServiceKey, - cursorColor: widget.editorStyle.cursorColor, - selectionColor: widget.editorStyle.selectionColor, - editorState: editorState, - editable: widget.editable, - child: AppFlowyInput( - key: editorState.service.inputServiceKey, + Widget _buildServices(BuildContext context) { + return Theme( + data: widget.themeData, + child: AppFlowyScroll( + key: editorState.service.scrollServiceKey, + child: Container( + color: editorStyle.backgroundColor, + padding: editorStyle.padding!, + child: AppFlowySelection( + key: editorState.service.selectionServiceKey, + cursorColor: editorStyle.cursorColor!, + selectionColor: editorStyle.selectionColor!, editorState: editorState, editable: widget.editable, - child: AppFlowyKeyboard( - key: editorState.service.keyboardServiceKey, - editable: widget.editable, - shortcutEvents: [ - ...widget.shortcutEvents, - ...builtInShortcutEvents, - ], + child: AppFlowyInput( + key: editorState.service.inputServiceKey, editorState: editorState, - child: FlowyToolbar( - key: editorState.service.toolbarServiceKey, + editable: widget.editable, + child: AppFlowyKeyboard( + key: editorState.service.keyboardServiceKey, + editable: widget.editable, + shortcutEvents: [ + ...widget.shortcutEvents, + ...builtInShortcutEvents, + ], editorState: editorState, - child: - editorState.service.renderPluginService.buildPluginWidget( - NodeWidgetContext( - context: context, - node: editorState.document.root, - editorState: editorState, + child: FlowyToolbar( + key: editorState.service.toolbarServiceKey, + editorState: editorState, + child: + editorState.service.renderPluginService.buildPluginWidget( + NodeWidgetContext( + context: context, + node: editorState.document.root, + editorState: editorState, + ), ), ), ), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart index 63aa61925f..3e385fe6d9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart @@ -41,7 +41,10 @@ void _handleCopy(EditorState editorState) async { Log.keyboard.debug('copy html: $htmlString'); RichClipboard.setData(RichClipboardData( html: htmlString, - text: textNode.toPlainText(), + text: textNode.toPlainText().substring( + selection.startIndex, + selection.endIndex, + ), )); } else { Log.keyboard.debug('unimplemented: copy non-text'); @@ -63,9 +66,19 @@ void _handleCopy(EditorState editorState) async { startOffset: selection.start.offset, endOffset: selection.end.offset, ).toHTMLString(); - final text = nodes - .map((node) => node is TextNode ? node.toPlainText() : '\n') - .join('\n'); + var text = ''; + for (final node in nodes) { + if (node is TextNode) { + if (node.path == selection.start.path) { + text += node.toPlainText().substring(selection.start.offset); + } else if (node.path == selection.end.path) { + text += node.toPlainText().substring(0, selection.end.offset); + } else { + text += node.toPlainText(); + } + } + text += '\n'; + } RichClipboard.setData(RichClipboardData(html: html, text: text)); } @@ -97,6 +110,7 @@ void _pasteHTML(EditorState editorState, String html) { final firstTextNode = firstNode as TextNode; tb.updateText( textNodeAtPath, (Delta()..retain(startOffset)) + firstTextNode.delta); + tb.updateNode(textNodeAtPath, firstTextNode.attributes); tb.afterSelection = (Selection.collapsed(Position( path: path, offset: startOffset + firstTextNode.delta.length))); editorState.apply(tb); @@ -114,7 +128,7 @@ void _pasteMultipleLinesInText( final firstNode = nodes[0]; final nodeAtPath = editorState.document.nodeAtPath(path)!; - if (nodeAtPath.type == "text" && firstNode.type == "text") { + if (nodeAtPath.type == 'text' && firstNode.type == 'text') { int? startNumber; if (nodeAtPath.subtype == BuiltInAttributeKey.numberList) { startNumber = nodeAtPath.attributes[BuiltInAttributeKey.number] as int; @@ -131,6 +145,7 @@ void _pasteMultipleLinesInText( ..retain(offset) ..delete(remain.length)) + firstTextNode.delta); + tb.updateNode(textNodeAtPath, firstTextNode.attributes); final tailNodes = nodes.sublist(1); final originalPath = [...path]; @@ -237,6 +252,31 @@ Delta _lineContentToDelta(String lineContent) { return delta; } +void _pasteMarkdown(EditorState editorState, String markdown) { + final selection = + editorState.service.selectionService.currentSelection.value?.normalized; + if (selection == null) { + return; + } + + final lines = markdown.split('\n'); + + if (lines.length == 1) { + _pasteSingleLine(editorState, selection, lines[0]); + return; + } + + var path = selection.end.path.next; + final node = editorState.document.nodeAtPath(selection.end.path); + if (node is TextNode && node.toPlainText().isEmpty) { + path = selection.end.path; + } + final document = markdownToDocument(markdown); + final transaction = editorState.transaction; + transaction.insertNodes(path, document.root.children); + editorState.apply(transaction); +} + void _handlePastePlainText(EditorState editorState, String plainText) { final selection = editorState.cursorSelection?.normalized; if (selection == null) { @@ -254,45 +294,7 @@ void _handlePastePlainText(EditorState editorState, String plainText) { // single line _pasteSingleLine(editorState, selection, lines.first); } else { - final firstLine = lines[0]; - final beginOffset = selection.end.offset; - final remains = lines.sublist(1); - - final path = [...selection.end.path]; - if (path.isEmpty) { - return; - } - - final node = - editorState.document.nodeAtPath(selection.end.path)! as TextNode; - final insertedLineSuffix = node.delta.slice(beginOffset); - - path[path.length - 1]++; - final tb = editorState.transaction; - final List nodes = - remains.map((e) => TextNode(delta: _lineContentToDelta(e))).toList(); - - final afterSelection = - _computeSelectionAfterPasteMultipleNodes(editorState, nodes); - - // append remain text to the last line - if (nodes.isNotEmpty) { - final last = nodes.last; - nodes[nodes.length - 1] = - TextNode(delta: last.delta..addAll(insertedLineSuffix)); - } - - // insert first line - tb.updateText( - node, - Delta() - ..retain(beginOffset) - ..insert(firstLine) - ..delete(node.delta.length - beginOffset)); - // insert remains - tb.insertNodes(path, nodes); - tb.afterSelection = afterSelection; - editorState.apply(tb); + _pasteMarkdown(editorState, plainText); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart index 44fe6d8587..41fbb88541 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/enter_without_shift_in_text_node_handler.dart @@ -153,11 +153,13 @@ ShortcutEventHandler enterWithoutShiftInTextNodesHandler = delta: textNode.delta.slice(selection.end.offset), ), ); - transaction.deleteText( - textNode, - selection.start.offset, - textNode.toPlainText().length - selection.start.offset, - ); + if (selection.end.offset != textNode.toPlainText().length) { + transaction.deleteText( + textNode, + selection.start.offset, + textNode.toPlainText().length - selection.start.offset, + ); + } if (textNode.children.isNotEmpty) { final children = textNode.children.toList(growable: false); transaction.deleteNodes(children); diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart index 47e2dc10ab..1535efdb94 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart @@ -57,7 +57,7 @@ ShortcutEventHandler formatHighlightEventHandler = (editorState, event) { } formatHighlight( editorState, - editorState.editorStyle.textStyle.highlightColorHex, + editorState.editorStyle.highlightColorHex!, ); return KeyEventResult.handled; }; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart index 1b37e5fa11..94caff83b1 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/markdown_syntax_to_styled_text.dart @@ -393,3 +393,48 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) { editorState.apply(transaction); return KeyEventResult.handled; }; + +ShortcutEventHandler underscoreToItalicHandler = (editorState, event) { + // Obtain the selection and selected nodes of the current document through the 'selectionService' + // to determine whether the selection is collapsed and whether the selected node is a text node. + final selectionService = editorState.service.selectionService; + final selection = selectionService.currentSelection.value; + final textNodes = selectionService.currentSelectedNodes.whereType(); + if (selection == null || !selection.isSingle || textNodes.length != 1) { + return KeyEventResult.ignored; + } + + final textNode = textNodes.first; + final text = textNode.toPlainText(); + // Determine if an 'underscore' already exists in the text node and only once. + final firstUnderscore = text.indexOf('_'); + final lastUnderscore = text.lastIndexOf('_'); + if (firstUnderscore == -1 || + firstUnderscore != lastUnderscore || + firstUnderscore == selection.start.offset - 1) { + return KeyEventResult.ignored; + } + + // Delete the previous 'underscore', + // update the style of the text surrounded by the two underscores to 'italic', + // and update the cursor position. + final transaction = editorState.transaction + ..deleteText(textNode, firstUnderscore, 1) + ..formatText( + textNode, + firstUnderscore, + selection.end.offset - firstUnderscore - 1, + { + BuiltInAttributeKey.italic: true, + }, + ) + ..afterSelection = Selection.collapsed( + Position( + path: textNode.path, + offset: selection.end.offset - 1, + ), + ); + editorState.apply(transaction); + + return KeyEventResult.handled; +}; diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart index 210a9a703e..8732c0f0ea 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/whitespace_handler.dart @@ -189,7 +189,8 @@ KeyEventResult _toHeadingStyle( int _countOfSign(String text, Selection selection) { for (var i = 6; i >= 0; i--) { - if (text.substring(0, selection.end.offset).contains('#' * i)) { + final heading = text.substring(0, selection.end.offset); + if (heading.contains('#' * i) && heading.length == i) { return i; } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart index 8501a16b41..38f4b9f8b3 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/render_plugin_service.dart @@ -78,10 +78,10 @@ class AppFlowyRenderPlugin extends AppFlowyRenderPluginService { node.key = key; return _autoUpdateNodeWidget(builder, context); } else { - assert(false, - 'Could not query the builder with this $name, or nodeValidator return false.'); - // TODO: return a placeholder widget with tips. - return Container(); + // Returns a SizeBox with 0 height if no builder found. + return const SizedBox( + height: 0, + ); } } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart index d68e686d61..f00edc4a56 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/scroll_service.dart @@ -92,11 +92,16 @@ class _AppFlowyScrollState extends State Widget build(BuildContext context) { return Listener( onPointerSignal: _onPointerSignal, - child: SingleChildScrollView( + child: CustomScrollView( key: _scrollViewKey, physics: const NeverScrollableScrollPhysics(), controller: _scrollController, - child: widget.child, + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: widget.child, + ) + ], ), ); } diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart index cca55527bf..86d39ba826 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/selection_service.dart @@ -1,7 +1,8 @@ +import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/infra/log.dart'; import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart'; import 'package:appflowy_editor/src/service/context_menu/context_menu.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:appflowy_editor/src/core/document/node.dart'; import 'package:appflowy_editor/src/core/document/node_iterator.dart'; @@ -512,9 +513,12 @@ class _AppFlowySelectionState extends State return; } + final baseOffset = + editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final offset = details.globalPosition + const Offset(10, 10) - baseOffset; final contextMenu = OverlayEntry( builder: (context) => ContextMenu( - position: details.globalPosition, + position: offset, editorState: editorState, items: builtInContextMenuItems, onPressed: () => _clearContextMenu(), diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart index 34aa469d85..88cbec35e2 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/shortcut_event/built_in_shortcut_events.dart @@ -220,7 +220,7 @@ List builtInShortcutEvents = [ ), ShortcutEvent( key: 'selection menu', - command: 'slash', + command: 'slash,shift+slash', handler: slashShortcutHandler, ), ShortcutEvent( @@ -285,6 +285,11 @@ List builtInShortcutEvents = [ command: 'escape', handler: exitEditingModeEventHandler, ), + ShortcutEvent( + key: 'Underscore to italic', + command: 'shift+underscore', + handler: underscoreToItalicHandler, + ), // https://github.com/flutter/flutter/issues/104944 // Workaround: Using space editing on the web platform often results in errors, // so adding a shortcut event to handle the space input instead of using the diff --git a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart index 3d3def574e..8991d0a30a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart +++ b/frontend/app_flowy/packages/appflowy_editor/lib/src/service/toolbar_service.dart @@ -1,5 +1,6 @@ +import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:appflowy_editor/src/render/toolbar/toolbar_item.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart'; diff --git a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml index a778fa8e91..04a31d29e5 100644 --- a/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_editor/pubspec.yaml @@ -1,6 +1,6 @@ name: appflowy_editor description: A highly customizable rich-text editor for Flutter -version: 0.0.6 +version: 0.0.7 homepage: https://github.com/AppFlowy-IO/AppFlowy platforms: @@ -27,6 +27,7 @@ dependencies: intl: flutter_localizations: sdk: flutter + markdown: ^6.0.1 dev_dependencies: flutter_test: diff --git a/frontend/app_flowy/packages/appflowy_editor/test/core/document/document_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/core/document/document_test.dart index a8059d584a..26e3a12c57 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/core/document/document_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/core/document/document_test.dart @@ -73,5 +73,66 @@ void main() async { final document = Document.fromJson(json); expect(document.toJson(), json); }); + + test('isEmpty', () { + expect( + true, + Document.fromJson({ + 'document': { + 'type': 'editor', + 'children': [ + { + 'type': 'text', + 'delta': [], + } + ], + } + }).isEmpty, + ); + + expect( + true, + Document.fromJson({ + 'document': { + 'type': 'editor', + 'children': [], + } + }).isEmpty, + ); + + expect( + true, + Document.fromJson({ + 'document': { + 'type': 'editor', + 'children': [ + { + 'type': 'text', + 'delta': [ + {'insert': ''} + ], + } + ], + } + }).isEmpty, + ); + + expect( + false, + Document.fromJson({ + 'document': { + 'type': 'editor', + 'children': [ + { + 'type': 'text', + 'delta': [ + {'insert': 'Welcome to AppFlowy!'} + ], + } + ], + } + }).isEmpty, + ); + }); }); } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart index 6396e47ebe..d371895487 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/infra/test_editor.dart @@ -39,7 +39,6 @@ class EditorWidgetTester { home: Scaffold( body: AppFlowyEditor( editorState: _editorState, - editorStyle: EditorStyle.defaultStyle(), ), ), ); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/delta_markdown_decoder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/delta_markdown_decoder_test.dart new file mode 100644 index 0000000000..206aa0ea0b --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/delta_markdown_decoder_test.dart @@ -0,0 +1,96 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('delta_markdown_decoder.dart', () { + test('bold', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.bold: true, + }), + ]); + final result = DeltaMarkdownDecoder().convert('Welcome to **AppFlowy**'); + expect(result, delta); + }); + + test('italic', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownDecoder().convert('Welcome to _AppFlowy_'); + expect(result, delta); + }); + + test('strikethrough', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.strikethrough: true, + }), + ]); + final result = DeltaMarkdownDecoder().convert('Welcome to ~~AppFlowy~~'); + expect(result, delta); + }); + + test('href', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + }), + ]); + final result = DeltaMarkdownDecoder() + .convert('Welcome to [AppFlowy](https://appflowy.io)'); + expect(result, delta); + }); + + test('code', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.code: true, + }), + ]); + final result = DeltaMarkdownDecoder().convert('Welcome to `AppFlowy`'); + expect(result, delta); + }); + + test('bold', () { + const markdown = + '***`Welcome`*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***'; + final delta = Delta(operations: [ + TextInsert('', attributes: { + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + }), + TextInsert('Welcome', attributes: { + BuiltInAttributeKey.code: true, + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + }), + TextInsert('', attributes: { + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + }), + TextInsert(' '), + TextInsert('to', attributes: { + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.strikethrough: true, + }), + TextInsert(' '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownDecoder().convert(markdown); + expect(result, delta); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/document_markdown_decoder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/document_markdown_decoder_test.dart new file mode 100644 index 0000000000..a95e7b877e --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/decoder/document_markdown_decoder_test.dart @@ -0,0 +1,126 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/src/plugins/markdown/decoder/document_markdown_decoder.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('document_markdown_decoder.dart', () { + const example = ''' +{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "attributes": {"subtype": "heading", "heading": "h2"}, + "delta": [ + {"insert": "👋 "}, + {"insert": "Welcome to", "attributes": {"bold": true}}, + {"insert": " "}, + { + "insert": "AppFlowy Editor", + "attributes": {"italic": true, "bold": true, "href": "appflowy.io"} + } + ] + }, + {"type": "text", "delta": []}, + { + "type": "text", + "delta": [ + {"insert": "AppFlowy Editor is a "}, + {"insert": "highly customizable", "attributes": {"bold": true}}, + {"insert": " "}, + {"insert": "rich-text editor", "attributes": {"italic": true}} + ] + }, + { + "type": "text", + "attributes": {"subtype": "checkbox", "checkbox": true}, + "delta": [{"insert": "Customizable"}] + }, + { + "type": "text", + "attributes": {"subtype": "checkbox", "checkbox": true}, + "delta": [{"insert": "Test-covered"}] + }, + { + "type": "text", + "attributes": {"subtype": "checkbox", "checkbox": false}, + "delta": [{"insert": "more to come!"}] + }, + {"type": "text", "delta": []}, + { + "type": "text", + "attributes": {"subtype": "quote"}, + "delta": [{"insert": "Here is an example you can give a try"}] + }, + {"type": "text", "delta": []}, + { + "type": "text", + "delta": [ + {"insert": "You can also use "}, + { + "insert": "AppFlowy Editor", + "attributes": {"italic": true, "bold": true} + }, + {"insert": " as a component to build your own app."} + ] + }, + {"type": "text", "delta": []}, + { + "type": "text", + "attributes": {"subtype": "bulleted-list"}, + "delta": [{"insert": "Use / to insert blocks"}] + }, + { + "type": "text", + "attributes": {"subtype": "bulleted-list"}, + "delta": [ + { + "insert": "Select text to trigger to the toolbar to format your notes." + } + ] + }, + {"type": "text", "delta": []}, + { + "type": "text", + "delta": [ + { + "insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!" + } + ] + }, + {"type": "text", "delta": []}, + {"type": "text", "delta": [{"insert": ""}]} + ] + } +} +'''; + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + test('parser document', () async { + const markdown = ''' +## 👋 **Welcome to** ***[AppFlowy Editor](appflowy.io)*** + +AppFlowy Editor is a **highly customizable** _rich-text editor_ +- [x] Customizable +- [x] Test-covered +- [ ] more to come! + +> Here is an example you can give a try + +You can also use ***AppFlowy Editor*** as a component to build your own app. + +* Use / to insert blocks +* Select text to trigger to the toolbar to format your notes. + +If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders! +'''; + final result = DocumentMarkdownDecoder().convert(markdown); + final data = Map.from(json.decode(example)); + expect(result.toJson(), data); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/delta_markdown_encoder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/delta_markdown_encoder_test.dart new file mode 100644 index 0000000000..831a449d02 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/delta_markdown_encoder_test.dart @@ -0,0 +1,100 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('delta_markdown_encoder.dart', () { + test('bold', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.bold: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to **AppFlowy**'); + }); + + test('italic', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to _AppFlowy_'); + }); + + test('underline', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.underline: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to AppFlowy'); + }); + + test('strikethrough', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.strikethrough: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to ~~AppFlowy~~'); + }); + + test('href', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to [AppFlowy](https://appflowy.io)'); + }); + + test('code', () { + final delta = Delta(operations: [ + TextInsert('Welcome to '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.code: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect(result, 'Welcome to `AppFlowy`'); + }); + + test('composition', () { + final delta = Delta(operations: [ + TextInsert('Welcome', attributes: { + BuiltInAttributeKey.code: true, + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.underline: true, + }), + TextInsert(' '), + TextInsert('to', attributes: { + BuiltInAttributeKey.italic: true, + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.strikethrough: true, + }), + TextInsert(' '), + TextInsert('AppFlowy', attributes: { + BuiltInAttributeKey.href: 'https://appflowy.io', + BuiltInAttributeKey.bold: true, + BuiltInAttributeKey.italic: true, + }), + ]); + final result = DeltaMarkdownEncoder().convert(delta); + expect( + result, + '***`Welcome`*** ***~~to~~*** ***[AppFlowy](https://appflowy.io)***', + ); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/document_markdown_encoder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/document_markdown_encoder_test.dart new file mode 100644 index 0000000000..0c3fdb0254 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/document_markdown_encoder_test.dart @@ -0,0 +1,136 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('document_markdown_encoder.dart', () { + const example = ''' +{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "attributes": { + "subtype": "heading", + "heading": "h2" + }, + "delta": [ + { "insert": "👋 " }, + { "insert": "Welcome to", "attributes": { "bold": true } }, + { "insert": " " }, + { + "insert": "AppFlowy Editor", + "attributes": { + "href": "appflowy.io", + "italic": true, + "bold": true + } + } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { "insert": "AppFlowy Editor is a " }, + { "insert": "highly customizable", "attributes": { "bold": true } }, + { "insert": " " }, + { "insert": "rich-text editor", "attributes": { "italic": true } }, + { "insert": " for " }, + { "insert": "Flutter", "attributes": { "underline": true } } + ] + }, + { + "type": "text", + "attributes": { "checkbox": true, "subtype": "checkbox" }, + "delta": [{ "insert": "Customizable" }] + }, + { + "type": "text", + "attributes": { "checkbox": true, "subtype": "checkbox" }, + "delta": [{ "insert": "Test-covered" }] + }, + { + "type": "text", + "attributes": { "checkbox": false, "subtype": "checkbox" }, + "delta": [{ "insert": "more to come!" }] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "attributes": { "subtype": "quote" }, + "delta": [{ "insert": "Here is an example you can give a try" }] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { "insert": "You can also use " }, + { + "insert": "AppFlowy Editor", + "attributes": { + "italic": true, + "bold": true, + "backgroundColor": "0x6000BCF0" + } + }, + { "insert": " as a component to build your own app." } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "attributes": { "subtype": "bulleted-list" }, + "delta": [{ "insert": "Use / to insert blocks" }] + }, + { + "type": "text", + "attributes": { "subtype": "bulleted-list" }, + "delta": [ + { + "insert": "Select text to trigger to the toolbar to format your notes." + } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { + "insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!" + } + ] + } + ] + } +} +'''; + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + test('parser document', () async { + final data = Map.from(json.decode(example)); + final document = Document.fromJson(data); + final result = DocumentMarkdownEncoder().convert(document); + expect(result, ''' +## 👋 **Welcome to** ***[AppFlowy Editor](appflowy.io)*** + +AppFlowy Editor is a **highly customizable** _rich-text editor_ for Flutter +- [x] Customizable +- [x] Test-covered +- [ ] more to come! + +> Here is an example you can give a try + +You can also use ***AppFlowy Editor*** as a component to build your own app. + +* Use / to insert blocks +* Select text to trigger to the toolbar to format your notes. + +If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!'''); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/image_node_parser_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/image_node_parser_test.dart new file mode 100644 index 0000000000..77102c8310 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/image_node_parser_test.dart @@ -0,0 +1,17 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('image_node_parser.dart', () { + test('parser image node', () { + final node = Node( + type: 'image', + attributes: { + 'image_src': 'https://appflowy.io', + }, + ); + final result = const ImageNodeParser().transform(node); + expect(result, '![](https://appflowy.io)'); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/text_node_parser_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/text_node_parser_test.dart new file mode 100644 index 0000000000..0d7c540a2b --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/plugins/markdown/encoder/parser/text_node_parser_test.dart @@ -0,0 +1,95 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + group('text_node_parser.dart', () { + const text = 'Welcome to AppFlowy'; + + test('heading style', () { + final h1 = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h1, + }, + ); + final h2 = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h2, + }, + ); + final h3 = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.heading, + BuiltInAttributeKey.heading: BuiltInAttributeKey.h3, + }, + ); + expect(const TextNodeParser().transform(h1), '# $text'); + expect(const TextNodeParser().transform(h2), '## $text'); + expect(const TextNodeParser().transform(h3), '### $text'); + }); + + test('bulleted list style', () { + final node = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.bulletedList, + }, + ); + expect(const TextNodeParser().transform(node), '* $text'); + }); + + test('number list style', () { + final node = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList, + BuiltInAttributeKey.number: 1, + }, + ); + expect(const TextNodeParser().transform(node), '1. $text'); + }); + + test('checkbox style', () { + final checkbox = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, + BuiltInAttributeKey.checkbox: true, + }, + ); + final unCheckbox = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.checkbox, + BuiltInAttributeKey.checkbox: false, + }, + ); + expect(const TextNodeParser().transform(checkbox), '- [x] $text'); + expect(const TextNodeParser().transform(unCheckbox), '- [ ] $text'); + }); + + test('quote style', () { + final node = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote, + }, + ); + expect(const TextNodeParser().transform(node), '> $text'); + }); + + test('code block style', () { + final node = TextNode( + delta: Delta(operations: [TextInsert(text)]), + attributes: { + BuiltInAttributeKey.subtype: 'code-block', + }, + ); + expect(const TextNodeParser().transform(node), '```\n$text\n```'); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart index a9732d8a20..201f07861a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/image/image_node_builder_test.dart @@ -49,10 +49,11 @@ void main() async { final editorRect = tester.getRect(editorFinder); final leftImageRect = tester.getRect(imageFinder.at(0)); - expect(leftImageRect.left, editor.editorState.editorStyle.padding.left); + expect( + leftImageRect.left, editor.editorState.editorStyle.padding!.left); final rightImageRect = tester.getRect(imageFinder.at(2)); expect(rightImageRect.right, - editorRect.right - editor.editorState.editorStyle.padding.right); + editorRect.right - editor.editorState.editorStyle.padding!.right); final centerImageRect = tester.getRect(imageFinder.at(1)); expect(centerImageRect.left, (leftImageRect.left + rightImageRect.left) / 2.0); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/link_menu/link_menu_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/link_menu/link_menu_test.dart index cef16a1cec..c20748779a 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/link_menu/link_menu_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/link_menu/link_menu_test.dart @@ -1,6 +1,8 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor/src/render/link_menu/link_menu.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; void main() async { setUpAll(() { @@ -39,5 +41,34 @@ void main() async { expect(submittedText, link); }); + + testWidgets('test tap linked text', (tester) async { + const link = 'appflowy.io'; + // This is a link [appflowy.io](appflowy.io) + final editor = tester.editor + ..insertTextNode( + null, + delta: Delta() + ..insert( + 'appflowy.io', + attributes: { + BuiltInAttributeKey.href: link, + }, + ), + ); + await editor.startTesting(); + final finder = find.byType(RichText); + expect(finder, findsOneWidget); + + // tap the link + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0, endOffset: link.length), + ); + await tester.tap(finder); + await tester.pumpAndSettle(const Duration(milliseconds: 350)); + final linkMenu = find.byType(LinkMenu); + expect(linkMenu, findsOneWidget); + expect(find.text(link, findRichText: true), findsNWidgets(2)); + }); }); } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart index f60d1610ad..d058fc2199 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/render/selection_menu/selection_menu_widget_test.dart @@ -137,7 +137,7 @@ Future _prepare(WidgetTester tester) async { ); for (final item in defaultSelectionMenuItems) { - expect(find.byWidget(item.icon), findsOneWidget); + expect(find.text(item.name()), findsOneWidget); } return Future.value(editor); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/default_text_operations/format_rich_text_style_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/default_text_operations/format_rich_text_style_test.dart new file mode 100644 index 0000000000..ccece41022 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/default_text_operations/format_rich_text_style_test.dart @@ -0,0 +1,36 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/src/render/rich_text/quoted_text.dart'; +import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('format_rich_text_style.dart', () { + testWidgets('formatTextNodes', (tester) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor..insertTextNode(text); + await editor.startTesting(); + await editor.updateSelection( + Selection.single(path: [0], startOffset: 0, endOffset: text.length), + ); + + // format the text to Quote + formatTextNodes(editor.editorState, { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote, + }); + await tester.pumpAndSettle(const Duration(seconds: 1)); + expect(find.byType(QuotedTextNodeWidget), findsOneWidget); + + // format the text to Quote again. The style should be removed. + formatTextNodes(editor.editorState, { + BuiltInAttributeKey.subtype: BuiltInAttributeKey.quote, + }); + await tester.pumpAndSettle(); + expect(find.byType(QuotedTextNodeWidget), findsNothing); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart index b3166a46fb..0b53b43af9 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/slash_handler_test.dart @@ -30,7 +30,7 @@ void main() async { ); for (final item in defaultSelectionMenuItems) { - expect(find.byWidget(item.icon), findsOneWidget); + expect(find.text(item.name()), findsOneWidget); } await editor.updateSelection(Selection.single(path: [1], startOffset: 0)); diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart index d06a865b64..f62d977ece 100644 --- a/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/internal_key_event_handlers/white_space_handler_test.dart @@ -213,5 +213,19 @@ void main() async { expect(textNode.attributes.check, true); expect(textNode.toPlainText(), insertedText); }); + + testWidgets('Presses # at the end of the text', (tester) async { + const text = 'Welcome to Appflowy 😁 #'; + final editor = tester.editor..insertTextNode(text); + await editor.startTesting(); + + final textNode = editor.nodeAtPath([0]) as TextNode; + await editor.updateSelection( + Selection.single(path: [0], startOffset: text.length), + ); + await editor.pressLogicKey(LogicalKeyboardKey.space); + expect(textNode.subtype, null); + expect(textNode.toPlainText(), text); + }); }); } diff --git a/frontend/app_flowy/packages/appflowy_editor/test/service/scroll_service_test.dart b/frontend/app_flowy/packages/appflowy_editor/test/service/scroll_service_test.dart new file mode 100644 index 0000000000..cf724a731c --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_editor/test/service/scroll_service_test.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('Testing Scroll With Gestures', () { + testWidgets('Test Gestsure Scroll', (tester) async { + final editor = tester.editor; + for (var i = 0; i < 100; i++) { + editor.insertTextNode('$i'); + } + editor.insertTextNode('mark'); + for (var i = 100; i < 200; i++) { + editor.insertTextNode('$i'); + } + await editor.startTesting(); + + final listFinder = find.byType(Scrollable); + final itemFinder = find.text('mark', findRichText: true); + + await tester.scrollUntilVisible(itemFinder, 500.0, + scrollable: listFinder); + + expect(itemFinder, findsOneWidget); + }); + }); +} diff --git a/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart b/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart index 6b666c278c..e62d857b3b 100644 --- a/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart +++ b/frontend/app_flowy/packages/appflowy_popover/lib/src/popover.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'package:appflowy_popover/src/layout.dart'; import 'package:flutter/material.dart'; import 'mask.dart'; @@ -96,7 +95,6 @@ class Popover extends StatefulWidget { class PopoverState extends State { static final RootOverlayEntry _rootEntry = RootOverlayEntry(); final PopoverLink popoverLink = PopoverLink(); - Timer? _debounceEnterRegionAction; @override void initState() { @@ -175,16 +173,9 @@ class PopoverState extends State { return MouseRegion( onEnter: (event) { - _debounceEnterRegionAction = - Timer(const Duration(milliseconds: 200), () { - if (widget.triggerActions & PopoverTriggerFlags.hover != 0) { - showOverlay(); - } - }); - }, - onExit: (event) { - _debounceEnterRegionAction?.cancel(); - _debounceEnterRegionAction = null; + if (widget.triggerActions & PopoverTriggerFlags.hover != 0) { + showOverlay(); + } }, child: Listener( child: widget.child, diff --git a/frontend/app_flowy/packages/flowy_infra/lib/color_extension.dart b/frontend/app_flowy/packages/flowy_infra/lib/color_extension.dart new file mode 100644 index 0000000000..4263cadd27 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_infra/lib/color_extension.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; + +@immutable +class AFThemeExtension extends ThemeExtension { + final Color? warning; + final Color? success; + + static Color tint1 = const Color(0xffe8e0ff); + static Color tint2 = const Color(0xffffe7fd); + static Color tint3 = const Color(0xffffe7ee); + static Color tint4 = const Color(0xffffefe3); + static Color tint5 = const Color(0xfffff2cd); + static Color tint6 = const Color(0xfff5ffdc); + static Color tint7 = const Color(0xffddffd6); + static Color tint8 = const Color(0xffdefff1); + static Color tint9 = const Color(0xffe1fbff); + + final Color greyHover; + final Color greySelect; + final Color lightGreyHover; + final Color toggleOffFill; + + final TextStyle code; + final TextStyle callout; + final TextStyle caption; + + const AFThemeExtension({ + required this.warning, + required this.success, + required this.greyHover, + required this.greySelect, + required this.lightGreyHover, + required this.toggleOffFill, + required this.code, + required this.callout, + required this.caption, + }); + + static AFThemeExtension of(BuildContext context) { + return Theme.of(context).extension()!; + } + + @override + AFThemeExtension copyWith({ + Color? warning, + Color? success, + Color? greyHover, + Color? greySelect, + Color? lightGreyHover, + Color? toggleOffFill, + TextStyle? code, + TextStyle? callout, + TextStyle? caption, + }) { + return AFThemeExtension( + warning: warning ?? this.warning, + success: success ?? this.success, + greyHover: greyHover ?? this.greyHover, + greySelect: greySelect ?? this.greySelect, + lightGreyHover: lightGreyHover ?? this.lightGreyHover, + toggleOffFill: toggleOffFill ?? this.toggleOffFill, + code: code ?? this.code, + callout: callout ?? this.callout, + caption: caption ?? this.caption, + ); + } + + @override + ThemeExtension lerp( + ThemeExtension? other, double t) { + if (other is! AFThemeExtension) { + return this; + } + return AFThemeExtension( + warning: Color.lerp(warning, other.warning, t), + success: Color.lerp(success, other.success, t), + greyHover: Color.lerp(greyHover, other.greyHover, t)!, + greySelect: Color.lerp(greySelect, other.greySelect, t)!, + lightGreyHover: Color.lerp(lightGreyHover, other.lightGreyHover, t)!, + toggleOffFill: Color.lerp(toggleOffFill, other.toggleOffFill, t)!, + code: other.code, + callout: other.callout, + caption: other.caption, + ); + } +} diff --git a/frontend/app_flowy/packages/flowy_infra/lib/language.dart b/frontend/app_flowy/packages/flowy_infra/lib/language.dart index bf0d72b1d3..3e18606815 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/language.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/language.dart @@ -40,6 +40,8 @@ String languageFromLocale(Locale locale) { return "Português"; case "ru": return "русский"; + case "sv": + return "Svenska"; case "tr": return "Türkçe"; diff --git a/frontend/app_flowy/packages/flowy_infra/lib/size.dart b/frontend/app_flowy/packages/flowy_infra/lib/size.dart index 4e54b7dc1c..5d6a71d6b4 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/size.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/size.dart @@ -43,6 +43,14 @@ class FontSizes { static double get s16 => 16 * scale; static double get s18 => 18 * scale; + + static double get s20 => 20 * scale; + + static double get s24 => 24 * scale; + + static double get s32 => 32 * scale; + + static double get s44 => 44 * scale; } class Sizes { diff --git a/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart b/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart index 9187e17f6e..9f477dcb0a 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/text_style.dart @@ -1,84 +1,135 @@ +import 'package:flowy_infra/size.dart'; import 'package:flutter/material.dart'; -import 'package:textstyle_extensions/textstyle_extensions.dart'; +// preserved until deprecation class Fonts { - static const String lato = "Lato"; + static String general = "Poppins"; - static const String quicksand = "Quicksand"; + static String monospace = "SF Mono"; - static const String emoji = "OpenSansEmoji"; + static String emoji = "Noto Color Emoji"; } -class FontSizes { - static double get scale => 1; - - static double get s11 => 11 * scale; - - static double get s12 => 12 * scale; - - static double get s14 => 14 * scale; - - static double get s16 => 16 * scale; - - static double get s18 => 18 * scale; -} - -// ignore: non_constant_identifier_names class TextStyles { - static const TextStyle lato = TextStyle( - fontFamily: Fonts.lato, - fontWeight: FontWeight.w400, - letterSpacing: 0, - height: 1, - fontFamilyFallback: [ - Fonts.emoji, - ], - ); + // preserved until deprecation + static TextStyle general({ + double? fontSize, + FontWeight fontWeight = FontWeight.w500, + Color? color, + }) => + TextStyle( + fontFamily: Fonts.general, + fontSize: fontSize ?? FontSizes.s12, + color: color, + fontWeight: fontWeight, + fontFamilyFallback: [Fonts.emoji], + letterSpacing: (fontSize ?? FontSizes.s12) * 0.005, + ); - static const TextStyle quicksand = TextStyle( - fontFamily: Fonts.quicksand, - fontWeight: FontWeight.w400, - fontFamilyFallback: [ - Fonts.emoji, - ], - ); + static TextStyle monospace({ + String? fontFamily, + double? fontSize, + FontWeight fontWeight = FontWeight.w400, + }) => + TextStyle( + fontFamily: fontFamily ?? Fonts.monospace, + fontSize: fontSize ?? FontSizes.s12, + fontWeight: fontWeight, + fontFamilyFallback: [Fonts.emoji], + ); - // ignore: non_constant_identifier_names - static TextStyle get T1 => quicksand.bold.size(FontSizes.s14).letterSpace(.7); + static TextStyle get title => general( + fontSize: FontSizes.s18, + fontWeight: FontWeight.w600, + ); - // ignore: non_constant_identifier_names - static TextStyle get T2 => lato.bold.size(FontSizes.s12).letterSpace(.4); + static TextStyle get subheading => general( + fontSize: FontSizes.s16, + fontWeight: FontWeight.w600, + ); - // ignore: non_constant_identifier_names - static TextStyle get H1 => lato.bold.size(FontSizes.s14); + static TextStyle get subtitle => general( + fontSize: FontSizes.s16, + fontWeight: FontWeight.w600, + ); - // ignore: non_constant_identifier_names - static TextStyle get H2 => lato.bold.size(FontSizes.s12); + static TextStyle get body1 => general( + fontSize: FontSizes.s12, + fontWeight: FontWeight.w500, + ); - // ignore: non_constant_identifier_names - static TextStyle get Body1 => lato.size(FontSizes.s14); + static TextStyle get body2 => general( + fontSize: FontSizes.s12, + fontWeight: FontWeight.w400, + ); - // ignore: non_constant_identifier_names - static TextStyle get Body2 => lato.size(FontSizes.s12); + static TextStyle get callout => general( + fontSize: FontSizes.s11, + fontWeight: FontWeight.w600, + ); - // ignore: non_constant_identifier_names - static TextStyle get Body3 => lato.size(FontSizes.s11); + static TextStyle get caption => general( + fontSize: FontSizes.s11, + fontWeight: FontWeight.w400, + ); - // ignore: non_constant_identifier_names - static TextStyle get Callout => quicksand.size(FontSizes.s14).letterSpace(1.75); + final String font; + final Color color; - // ignore: non_constant_identifier_names - static TextStyle get CalloutFocus => Callout.bold; + TextStyles({ + required this.font, + required this.color, + }); - // ignore: non_constant_identifier_names - static TextStyle get Btn => quicksand.bold.size(FontSizes.s16).letterSpace(1.75); + TextStyle getFontStyle({ + String? fontFamily, + double? fontSize, + FontWeight? fontWeight, + Color? fontColor, + double? letterSpacing, + double? lineHeight, + }) => + TextStyle( + fontFamily: fontFamily ?? font, + fontSize: fontSize ?? FontSizes.s12, + color: fontColor ?? color, + fontWeight: fontWeight ?? FontWeight.w500, + fontFamilyFallback: const ["Noto Color Emoji"], + letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005), + height: lineHeight, + ); - // ignore: non_constant_identifier_names - static TextStyle get BtnSelected => quicksand.size(FontSizes.s14).letterSpace(1.75); - - // ignore: non_constant_identifier_names - static TextStyle get Footnote => quicksand.bold.size(FontSizes.s11); - - // ignore: non_constant_identifier_names - static TextStyle get Caption => lato.size(FontSizes.s11).letterSpace(.3); + TextTheme generateTextTheme() { + return TextTheme( + displayLarge: getFontStyle( + fontSize: FontSizes.s32, + fontWeight: FontWeight.w600, + lineHeight: 42.0, + ), // h2 + displayMedium: getFontStyle( + fontSize: FontSizes.s24, + fontWeight: FontWeight.w600, + lineHeight: 34.0, + ), // h3 + displaySmall: getFontStyle( + fontSize: FontSizes.s20, + fontWeight: FontWeight.w600, + lineHeight: 28.0, + ), // h4 + titleLarge: getFontStyle( + fontSize: FontSizes.s18, + fontWeight: FontWeight.w600, + ), // title + titleMedium: getFontStyle( + fontSize: FontSizes.s16, + fontWeight: FontWeight.w600, + ), // heading + titleSmall: getFontStyle( + fontSize: FontSizes.s14, + fontWeight: FontWeight.w600, + ), // subheading + bodyMedium: getFontStyle(), // body-regular + bodySmall: getFontStyle(fontWeight: FontWeight.w400), // body-thin + ); + } } diff --git a/frontend/app_flowy/packages/flowy_infra/lib/theme.dart b/frontend/app_flowy/packages/flowy_infra/lib/theme.dart index cba65aa7fc..8e845ea324 100644 --- a/frontend/app_flowy/packages/flowy_infra/lib/theme.dart +++ b/frontend/app_flowy/packages/flowy_infra/lib/theme.dart @@ -1,35 +1,34 @@ +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; -enum ThemeType { - light, - dark, -} +import 'color_extension.dart'; -ThemeType themeTypeFromString(String name) { - ThemeType themeType = ThemeType.light; +Brightness themeTypeFromString(String name) { + Brightness themeType = Brightness.light; if (name == "dark") { - themeType = ThemeType.dark; + themeType = Brightness.dark; } return themeType; } -String themeTypeToString(ThemeType ty) { - switch (ty) { - case ThemeType.light: +String themeTypeToString(Brightness brightness) { + switch (brightness) { + case Brightness.light: return "light"; - case ThemeType.dark: + case Brightness.dark: return "dark"; } } -//Color Pallettes +// Color Palettes const _black = Color(0xff000000); const _white = Color(0xFFFFFFFF); class AppTheme { - ThemeType ty; - bool isDark; - late Color surface; // + Brightness brightness; + + late Color surface; late Color hover; late Color selector; late Color red; @@ -58,6 +57,7 @@ class AppTheme { late Color tint7; late Color tint8; late Color tint9; + late Color textColor; late Color iconColor; late Color disableIconColor; @@ -65,22 +65,24 @@ class AppTheme { late Color main1; late Color main2; - late Color shadowColor; + late Color shadow; + + late String font; + late String monospaceFont; /// Default constructor - AppTheme({required this.ty, this.isDark = false}); + AppTheme({this.brightness = Brightness.light}); - factory AppTheme.fromName({required String name}) { - return AppTheme.fromType(themeTypeFromString(name)); - } - - /// fromType factory constructor - factory AppTheme.fromType(ThemeType themeType) { - switch (themeType) { - case ThemeType.light: - return AppTheme(ty: themeType, isDark: false) + factory AppTheme.fromName({ + required String themeName, + required String font, + required String monospaceFont, + }) { + switch (themeTypeFromString(themeName)) { + case Brightness.light: + return AppTheme(brightness: Brightness.light) ..surface = Colors.white - ..hover = const Color(0xFFe0f8ff) // + ..hover = const Color(0xFFe0f8ff) ..selector = const Color(0xfff2fcff) ..red = const Color(0xfffb006d) ..yellow = const Color(0xffffd667) @@ -109,11 +111,13 @@ class AppTheme { ..main2 = const Color(0xff00b7ea) ..textColor = _black ..iconColor = _black - ..shadowColor = _black - ..disableIconColor = const Color(0xffbdbdbd); + ..shadow = _black + ..disableIconColor = const Color(0xffbdbdbd) + ..font = font + ..monospaceFont = monospaceFont; - case ThemeType.dark: - return AppTheme(ty: themeType, isDark: true) + case Brightness.dark: + return AppTheme(brightness: Brightness.dark) ..surface = const Color(0xff292929) ..hover = const Color(0xff1f1f1f) ..selector = const Color(0xff333333) @@ -144,44 +148,78 @@ class AppTheme { ..main2 = const Color(0xff009cc7) ..textColor = _white ..iconColor = _white - ..shadowColor = _white - ..disableIconColor = const Color(0xff333333); + ..shadow = _black + ..disableIconColor = const Color(0xff333333) + ..font = font + ..monospaceFont = monospaceFont; } } ThemeData get themeData { - var t = ThemeData( - textTheme: TextTheme(bodyText2: TextStyle(color: textColor)), + final textTheme = TextStyles(font: font, color: shader1); + return ThemeData( + brightness: brightness, + textTheme: textTheme.generateTextTheme(), textSelectionTheme: TextSelectionThemeData( cursorColor: main2, selectionHandleColor: main2), primaryIconTheme: IconThemeData(color: hover), iconTheme: IconThemeData(color: shader1), + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all(Colors.transparent), + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, canvasColor: shader6, - //Don't use this property because of the redo/undo button in the toolbar use the hoverColor. - // hoverColor: main2, + dividerColor: shader6, + hintColor: shader3, + disabledColor: shader4, + highlightColor: main1, + indicatorColor: main1, + toggleableActiveColor: main1, colorScheme: ColorScheme( - brightness: isDark ? Brightness.dark : Brightness.light, - primary: main1, - secondary: main2, - background: surface, - surface: surface, - onBackground: surface, - onSurface: surface, - onError: red, - onPrimary: bg1, - onSecondary: bg1, - error: red), + brightness: brightness, + primary: main1, + onPrimary: shader7, + primaryContainer: main2, + onPrimaryContainer: shader7, + secondary: hover, + onSecondary: shader1, + secondaryContainer: selector, + onSecondaryContainer: shader1, + background: surface, + onBackground: shader1, + surface: surface, + onSurface: shader1, + onError: shader7, + error: red, + outline: shader4, + surfaceVariant: bg1, + shadow: shadow, + ), + extensions: [ + AFThemeExtension( + warning: yellow, + success: green, + greyHover: bg2, + greySelect: bg3, + lightGreyHover: shader6, + toggleOffFill: shader5, + code: textTheme.getFontStyle(fontFamily: monospaceFont), + callout: textTheme.getFontStyle( + fontSize: FontSizes.s11, + fontColor: shader3, + ), + caption: textTheme.getFontStyle( + fontSize: FontSizes.s11, + fontWeight: FontWeight.w400, + fontColor: shader3, + ), + ) + ], ); - - return t.copyWith( - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - highlightColor: main1, - indicatorColor: main1, - toggleableActiveColor: main1); } Color shift(Color c, double d) => - ColorUtils.shiftHsl(c, d * (isDark ? -1 : 1)); + ColorUtils.shiftHsl(c, d * (brightness == Brightness.dark ? -1 : 1)); } class ColorUtils { diff --git a/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock index 8ddcc99719..398e3d0c89 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_infra_ui/example/pubspec.lock @@ -8,6 +8,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + appflowy_popover: + dependency: transitive + description: + path: "../../appflowy_popover" + relative: true + source: path + version: "0.0.1" async: dependency: transitive description: @@ -234,7 +241,7 @@ packages: source: hosted version: "2.0.1" provider: - dependency: transitive + dependency: "direct main" description: name: provider url: "https://pub.dartlang.org" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock index 7309cce94a..f736121e48 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.lock @@ -61,7 +61,7 @@ packages: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -73,7 +73,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.1" matcher: dependency: transitive description: @@ -164,5 +164,5 @@ packages: source: hosted version: "2.1.2" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock index bdc4a1ae65..e05d8bd65c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.lock @@ -68,7 +68,7 @@ packages: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -92,7 +92,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.1" matcher: dependency: transitive description: @@ -183,5 +183,5 @@ packages: source: hosted version: "2.1.2" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart index a98ac0c855..d1d5be88f6 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart @@ -1,9 +1,7 @@ import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/decoration.dart'; -import 'package:provider/provider.dart'; class AppFlowyPopover extends StatelessWidget { final Widget child; @@ -69,10 +67,9 @@ class _PopoverContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); final decoration = FlowyDecoration.decoration( - theme.surface, - theme.shadowColor.withOpacity(0.15), + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.shadow.withOpacity(0.15), ); return Material( diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart index a7e7523dd9..eecc81283c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart @@ -2,9 +2,7 @@ import 'dart:math'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/style_widget/decoration.dart'; -import 'package:provider/provider.dart'; class ListOverlayFooter { Widget widget; @@ -124,15 +122,13 @@ class OverlayContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = - context.watch() ?? AppTheme.fromType(ThemeType.light); return Material( type: MaterialType.transparency, child: Container( padding: padding, decoration: FlowyDecoration.decoration( - theme.surface, - theme.shadowColor.withOpacity(0.15), + Theme.of(context).colorScheme.surface, + Theme.of(context).colorScheme.shadow.withOpacity(0.15), ), constraints: constraints, child: child, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart index 616a012940..340dbb28b7 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -1,7 +1,9 @@ +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class FlowyButton extends StatelessWidget { final Widget text; @@ -10,7 +12,7 @@ class FlowyButton extends StatelessWidget { final EdgeInsets margin; final Widget? leftIcon; final Widget? rightIcon; - final Color hoverColor; + final Color? hoverColor; final bool isSelected; final BorderRadius radius; @@ -22,19 +24,20 @@ class FlowyButton extends StatelessWidget { this.margin = const EdgeInsets.symmetric(horizontal: 6, vertical: 2), this.leftIcon, this.rightIcon, - this.hoverColor = Colors.transparent, + this.hoverColor, this.isSelected = false, this.radius = const BorderRadius.all(Radius.circular(6)), }) : super(key: key); @override Widget build(BuildContext context) { - return InkWell( + return GestureDetector( + behavior: HitTestBehavior.opaque, onTap: onTap, child: FlowyHover( style: HoverStyle( borderRadius: radius, - hoverColor: hoverColor, + hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary, ), onHover: onHover, isSelected: () => isSelected, @@ -135,7 +138,7 @@ class FlowyTextButton extends StatelessWidget { shape: RoundedRectangleBorder( borderRadius: radius ?? BorderRadius.circular(2)), fillColor: fillColor, - hoverColor: hoverColor ?? Colors.transparent, + hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary, focusColor: Colors.transparent, splashColor: Colors.transparent, highlightColor: Colors.transparent, @@ -147,6 +150,7 @@ class FlowyTextButton extends StatelessWidget { if (tooltip != null) { child = Tooltip( message: tooltip!, + textStyle: TextStyles.caption.textColor(Colors.white), child: child, ); } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart index 5df1d56868..8d8b64dcf3 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -5,7 +5,7 @@ import 'package:flowy_infra/time/duration.dart'; typedef HoverBuilder = Widget Function(BuildContext context, bool onHover); class FlowyHover extends StatefulWidget { - final HoverStyle style; + final HoverStyle? style; final HoverBuilder? builder; final Widget? child; @@ -25,7 +25,7 @@ class FlowyHover extends StatefulWidget { Key? key, this.builder, this.child, - required this.style, + this.style, this.isSelected, this.onHover, this.cursor, @@ -51,7 +51,7 @@ class _FlowyHoverState extends State { return MouseRegion( cursor: widget.cursor != null ? widget.cursor! : SystemMouseCursors.click, opaque: false, - onEnter: (p) { + onHover: (p) { if (_onHover) return; if (widget.buildWhenOnHover?.call() ?? true) { @@ -82,13 +82,15 @@ class _FlowyHoverState extends State { } final child = widget.child ?? widget.builder!(context, _onHover); + final style = widget.style ?? + HoverStyle(hoverColor: Theme.of(context).colorScheme.secondary); if (showHover) { return FlowyHoverContainer( - style: widget.style, + style: style, child: child, ); } else { - return Container(color: widget.style.backgroundColor, child: child); + return Container(color: style.backgroundColor, child: child); } } } @@ -96,18 +98,19 @@ class _FlowyHoverState extends State { class HoverStyle { final Color borderColor; final double borderWidth; - final Color hoverColor; + final Color? hoverColor; final BorderRadius borderRadius; final EdgeInsets contentMargin; final Color backgroundColor; - const HoverStyle( - {this.borderColor = Colors.transparent, - this.borderWidth = 0, - this.borderRadius = const BorderRadius.all(Radius.circular(6)), - this.contentMargin = EdgeInsets.zero, - this.backgroundColor = Colors.transparent, - required this.hoverColor}); + const HoverStyle({ + this.borderColor = Colors.transparent, + this.borderWidth = 0, + this.borderRadius = const BorderRadius.all(Radius.circular(6)), + this.contentMargin = EdgeInsets.zero, + this.backgroundColor = Colors.transparent, + this.hoverColor, + }); } class FlowyHoverContainer extends StatelessWidget { @@ -131,7 +134,7 @@ class FlowyHoverContainer extends StatelessWidget { margin: style.contentMargin, decoration: BoxDecoration( border: hoverBorder, - color: style.hoverColor, + color: style.hoverColor ?? Theme.of(context).colorScheme.secondary, borderRadius: style.borderRadius, ), child: child, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart index 0f56541516..bf33bb4f15 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/icon_button.dart @@ -20,7 +20,7 @@ class FlowyIconButton extends StatelessWidget { this.height, this.onPressed, this.fillColor = Colors.transparent, - this.hoverColor = Colors.transparent, + this.hoverColor, this.iconPadding = EdgeInsets.zero, this.radius, this.tooltipText, @@ -35,11 +35,13 @@ class FlowyIconButton extends StatelessWidget { assert(size.width > iconPadding.horizontal); assert(size.height > iconPadding.vertical); - final childWidth = min(size.width - iconPadding.horizontal, size.height - iconPadding.vertical); + final childWidth = min(size.width - iconPadding.horizontal, + size.height - iconPadding.vertical); final childSize = Size(childWidth, childWidth); return ConstrainedBox( - constraints: BoxConstraints.tightFor(width: size.width, height: size.height), + constraints: + BoxConstraints.tightFor(width: size.width, height: size.height), child: Tooltip( message: tooltipText ?? '', showDuration: Duration.zero, @@ -47,9 +49,10 @@ class FlowyIconButton extends StatelessWidget { visualDensity: VisualDensity.compact, hoverElevation: 0, highlightElevation: 0, - shape: RoundedRectangleBorder(borderRadius: radius ?? BorderRadius.circular(2)), + shape: RoundedRectangleBorder( + borderRadius: radius ?? BorderRadius.circular(2)), fillColor: fillColor, - hoverColor: hoverColor, + hoverColor: hoverColor ?? Theme.of(context).colorScheme.secondary, focusColor: Colors.transparent, splashColor: Colors.transparent, highlightColor: Colors.transparent, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart index 619d8b1eaa..91d1890314 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scroll_bar.dart @@ -1,11 +1,10 @@ import 'dart:math'; import 'dart:async'; import 'package:async/async.dart'; +import 'package:flowy_infra/color_extension.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/widget/mouse_hover_builder.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; class StyledScrollbar extends StatefulWidget { @@ -83,7 +82,6 @@ class ScrollbarState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); return LayoutBuilder( builder: (_, BoxConstraints constraints) { double maxExtent; @@ -139,12 +137,14 @@ class ScrollbarState extends State { // Handle color var handleColor = widget.handleColor ?? - (theme.isDark ? theme.bg2.withOpacity(.2) : theme.bg2); + (Theme.of(context).brightness == Brightness.dark + ? AFThemeExtension.of(context).greyHover.withOpacity(.2) + : AFThemeExtension.of(context).greyHover); // Track color var trackColor = widget.trackColor ?? - (theme.isDark - ? theme.bg2.withOpacity(.1) - : theme.bg2.withOpacity(.3)); + (Theme.of(context).brightness == Brightness.dark + ? AFThemeExtension.of(context).greyHover.withOpacity(.1) + : AFThemeExtension.of(context).greyHover.withOpacity(.3)); //Layout the stack, it just contains a child, and return Stack(children: [ diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart index 46bf5eea14..9db5b768f9 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -1,6 +1,5 @@ -import 'package:flowy_infra/theme.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; class FlowyText extends StatelessWidget { final String title; @@ -57,17 +56,15 @@ class FlowyText extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return Text( title, maxLines: maxLines, textAlign: textAlign, overflow: overflow ?? TextOverflow.clip, - style: TextStyle( - color: color ?? theme.textColor, - fontWeight: fontWeight, + style: TextStyles.general( fontSize: fontSize, - fontFamily: 'Mulish', + fontWeight: fontWeight, + color: color, ), ); } diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart index 5e72c30196..7a0c0f4508 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/text_input.dart @@ -2,15 +2,14 @@ import 'dart:async'; import 'dart:math' as math; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/text_style.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; // ignore: import_of_legacy_library_into_null_safe 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; @@ -52,7 +51,7 @@ class FlowyFormTextInput extends StatelessWidget { initialValue: initialValue, onChanged: onChanged, onFocusCreated: onFocusCreated, - style: textStyle ?? TextStyles.Body1, + style: textStyle ?? TextStyles.body1, onEditingComplete: onEditingComplete, onFocusChanged: onFocusChanged, controller: controller, @@ -60,7 +59,8 @@ 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, ), @@ -141,7 +141,8 @@ 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) { @@ -157,7 +158,8 @@ 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()); @@ -180,7 +182,6 @@ class StyledSearchTextInputState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); return Container( padding: EdgeInsets.symmetric(vertical: Insets.sm), child: TextFormField( @@ -195,8 +196,8 @@ class StyledSearchTextInputState extends State { obscureText: widget.obscureText ?? false, autocorrect: widget.autoCorrect ?? false, enableSuggestions: widget.enableSuggestions ?? false, - style: widget.style ?? TextStyles.Body1, - cursorColor: theme.main1, + style: widget.style ?? TextStyles.body1, + cursorColor: Theme.of(context).colorScheme.primary, controller: _controller, showCursor: true, enabled: widget.enabled, @@ -206,14 +207,16 @@ 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), errorText: widget.errorText, errorMaxLines: 2, hintText: widget.hintText, - hintStyle: TextStyles.Body1.textColor(theme.shader4), + hintStyle: + TextStyles.body1.textColor(Theme.of(context).hintColor), labelText: widget.label), ), ); @@ -254,7 +257,8 @@ 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, @@ -274,7 +278,8 @@ 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 @@ -285,7 +290,8 @@ 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( @@ -300,7 +306,8 @@ 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), @@ -326,7 +333,8 @@ 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/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart index d251f993fd..990506d96e 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart @@ -1,8 +1,6 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/text_style.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; class BaseStyledButton extends StatefulWidget { final Widget child; @@ -72,23 +70,25 @@ class BaseStyledBtnState extends State { @override Widget build(BuildContext context) { - final theme = context.watch(); return Container( decoration: BoxDecoration( - color: widget.bgColor ?? theme.surface, + color: widget.bgColor ?? Theme.of(context).colorScheme.surface, borderRadius: widget.borderRadius ?? Corners.s10Border, boxShadow: _isFocused ? [ BoxShadow( - color: theme.shader6, - offset: Offset.zero, - blurRadius: 8.0, - spreadRadius: 0.0), + color: Theme.of(context).colorScheme.shadow, + offset: Offset.zero, + blurRadius: 8.0, + spreadRadius: 0.0, + ), BoxShadow( - color: widget.bgColor ?? theme.surface, - offset: Offset.zero, - blurRadius: 8.0, - spreadRadius: -4.0), + color: + widget.bgColor ?? Theme.of(context).colorScheme.surface, + offset: Offset.zero, + blurRadius: 8.0, + spreadRadius: -4.0, + ), ] : [], ), @@ -97,7 +97,7 @@ class BaseStyledBtnState extends State { shape: RoundedRectangleBorder( side: BorderSide( width: 1.8, - color: theme.shader6, + color: Theme.of(context).colorScheme.outline, ), borderRadius: widget.borderRadius ?? Corners.s10Border, ), @@ -106,7 +106,7 @@ class BaseStyledBtnState extends State { child: RawMaterialButton( focusNode: _focusNode, autofocus: widget.autoFocus, - textStyle: widget.useBtnText ? TextStyles.Btn : null, + textStyle: widget.useBtnText ? TextStyles.body1 : null, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, // visualDensity: VisualDensity.compact, splashColor: Colors.transparent, @@ -116,8 +116,10 @@ class BaseStyledBtnState extends State { highlightElevation: 0, focusElevation: 0, fillColor: Colors.transparent, - hoverColor: widget.hoverColor ?? theme.hover, - highlightColor: widget.downColor ?? theme.main1, + hoverColor: + widget.hoverColor ?? Theme.of(context).colorScheme.secondary, + highlightColor: + widget.downColor ?? Theme.of(context).colorScheme.primary, focusColor: widget.focusColor ?? Colors.grey.withOpacity(0.35), constraints: BoxConstraints( minHeight: widget.minHeight ?? 0, minWidth: widget.minWidth ?? 0), diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart index 63e9ce2698..1f7491f133 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/primary_button.dart @@ -1,8 +1,6 @@ import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'base_styled_button.dart'; class PrimaryTextButton extends StatelessWidget { @@ -16,13 +14,12 @@ class PrimaryTextButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return PrimaryButton( bigMode: bigMode, onPressed: onPressed, child: FlowyText.regular( label, - color: theme.surface, + color: Theme.of(context).colorScheme.onPrimary, ), ); } @@ -39,14 +36,13 @@ class PrimaryButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BaseStyledButton( minWidth: bigMode ? 100 : 80, minHeight: bigMode ? 40 : 38, contentPadding: EdgeInsets.zero, - bgColor: theme.main1, - hoverColor: theme.main1, - downColor: theme.main1, + bgColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primaryContainer, + downColor: Theme.of(context).colorScheme.primary, borderRadius: bigMode ? Corners.s12Border : Corners.s8Border, onPressed: onPressed, child: child, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart index ef7a6e6051..3203a7d75d 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart @@ -1,9 +1,7 @@ import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; // ignore: import_of_legacy_library_into_null_safe import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'base_styled_button.dart'; class SecondaryTextButton extends StatelessWidget { @@ -17,13 +15,12 @@ class SecondaryTextButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return SecondaryButton( bigMode: bigMode, onPressed: onPressed, child: FlowyText.regular( label, - color: theme.main1, + color: Theme.of(context).colorScheme.primary, ), ); } @@ -40,15 +37,14 @@ class SecondaryButton extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); return BaseStyledButton( minWidth: bigMode ? 100 : 80, minHeight: bigMode ? 40 : 38, contentPadding: EdgeInsets.zero, - bgColor: theme.shader7, - hoverColor: theme.hover, - downColor: theme.main1, - outlineColor: theme.main1, + bgColor: Theme.of(context).colorScheme.surface, + hoverColor: Theme.of(context).colorScheme.secondary, + downColor: Theme.of(context).colorScheme.primary, + outlineColor: Theme.of(context).colorScheme.primary, borderRadius: bigMode ? Corners.s12Border : Corners.s8Border, onPressed: onPressed, child: child, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart index 5cf1641649..f9ae82b90d 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart @@ -1,10 +1,8 @@ import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/widget/dialog/dialog_size.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; import 'dart:ui'; extension IntoDialog on Widget { @@ -50,11 +48,9 @@ class StyledDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = context.watch(); - Widget innerContent = Container( padding: padding ?? EdgeInsets.all(Insets.lGutter), - color: bgColor ?? theme.shader7, + color: bgColor ?? Theme.of(context).colorScheme.surface, child: child, ); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart index 33075f703c..936993dd6c 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_button.dart @@ -1,4 +1,5 @@ import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flutter/material.dart'; class RoundedTextButton extends StatelessWidget { @@ -8,7 +9,7 @@ class RoundedTextButton extends StatelessWidget { final double? height; final BorderRadius borderRadius; final Color borderColor; - final Color color; + final Color? color; final Color textColor; final double fontSize; @@ -20,7 +21,7 @@ class RoundedTextButton extends StatelessWidget { this.height, this.borderRadius = Corners.s12Border, this.borderColor = Colors.transparent, - this.color = Colors.transparent, + this.color, this.textColor = Colors.white, this.fontSize = 16, }) : super(key: key); @@ -38,14 +39,17 @@ class RoundedTextButton extends StatelessWidget { decoration: BoxDecoration( border: Border.all(color: borderColor), borderRadius: borderRadius, - color: color, + color: color ?? Theme.of(context).colorScheme.primary, ), child: SizedBox.expand( child: TextButton( onPressed: onPressed, child: Text( title ?? '', - style: TextStyle(color: textColor, fontSize: fontSize), + style: TextStyles.general( + fontSize: fontSize, + color: textColor, + ), ), ), ), @@ -80,9 +84,8 @@ class RoundedImageButton extends StatelessWidget { child: TextButton( onPressed: press, style: ButtonStyle( - shape: MaterialStateProperty.all(RoundedRectangleBorder( - borderRadius: borderRadius, - ))), + shape: MaterialStateProperty.all( + RoundedRectangleBorder(borderRadius: borderRadius))), child: child, ), ); diff --git a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart index e5f6b8899a..501fb561f3 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart +++ b/frontend/app_flowy/packages/flowy_infra_ui/lib/widget/rounded_input_field.dart @@ -1,17 +1,19 @@ import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra/text_style.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flutter/material.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:flutter/services.dart'; +import 'package:textstyle_extensions/textstyle_extensions.dart'; class RoundedInputField extends StatefulWidget { final String? hintText; final bool obscureText; final Widget? obscureIcon; final Widget? obscureHideIcon; - final Color normalBorderColor; - final Color errorBorderColor; - final Color cursorColor; + final Color? normalBorderColor; + final Color? errorBorderColor; + final Color? cursorColor; final Color? focusBorderColor; final String errorText; final TextStyle style; @@ -37,10 +39,10 @@ class RoundedInputField extends StatefulWidget { this.obscureHideIcon, this.onChanged, this.onEditingComplete, - this.normalBorderColor = Colors.transparent, - this.errorBorderColor = Colors.transparent, + this.normalBorderColor, + this.errorBorderColor, this.focusBorderColor, - this.cursorColor = Colors.black, + this.cursorColor, this.style = const TextStyle(fontSize: 20, fontWeight: FontWeight.w500), this.margin = EdgeInsets.zero, this.padding = EdgeInsets.zero, @@ -74,11 +76,13 @@ class _RoundedInputFieldState extends State { @override Widget build(BuildContext context) { - var borderColor = widget.normalBorderColor; - var focusBorderColor = widget.focusBorderColor ?? borderColor; + var borderColor = + widget.normalBorderColor ?? Theme.of(context).colorScheme.outline; + var focusBorderColor = + widget.focusBorderColor ?? Theme.of(context).colorScheme.primary; if (widget.errorText.isNotEmpty) { - borderColor = widget.errorBorderColor; + borderColor = Theme.of(context).colorScheme.error; focusBorderColor = borderColor; } @@ -107,13 +111,14 @@ class _RoundedInputFieldState extends State { widget.onEditingComplete!(inputText); } }, - cursorColor: widget.cursorColor, + cursorColor: + widget.cursorColor ?? Theme.of(context).colorScheme.primary, obscureText: obscuteText, style: widget.style, decoration: InputDecoration( contentPadding: widget.contentPadding, hintText: widget.hintText, - hintStyle: TextStyle(color: widget.normalBorderColor), + hintStyle: TextStyles.body1.textColor(borderColor), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: borderColor, diff --git a/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock b/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock index 143279950d..04805bd707 100644 --- a/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_infra_ui/pubspec.lock @@ -8,8 +8,15 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + appflowy_popover: + dependency: "direct main" + description: + path: "../appflowy_popover" + relative: true + source: path + version: "0.0.1" async: - dependency: transitive + dependency: "direct main" description: name: async url: "https://pub.dartlang.org" diff --git a/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock b/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock index ef6b2235e4..4caaa46dea 100644 --- a/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_sdk/example/pubspec.lock @@ -70,7 +70,7 @@ packages: name: dartz url: "https://pub.dartlang.org" source: hosted - version: "0.10.0-nullsafety.2" + version: "0.10.1" fake_async: dependency: transitive description: @@ -122,7 +122,7 @@ packages: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -165,7 +165,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.1" logger: dependency: transitive description: @@ -305,5 +305,5 @@ packages: source: hosted version: "3.0.0" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" diff --git a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart index 0716a2f4c6..020c8f1ed0 100644 --- a/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart +++ b/frontend/app_flowy/packages/flowy_sdk/lib/dispatch/dispatch.dart @@ -18,7 +18,6 @@ import 'package:flowy_sdk/protobuf/dart-ffi/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-document/protobuf.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; -import 'package:flowy_sdk/protobuf/flowy-sync/protobuf.dart'; // ignore: unused_import import 'package:protobuf/protobuf.dart'; diff --git a/frontend/app_flowy/packages/flowy_sdk/pubspec.lock b/frontend/app_flowy/packages/flowy_sdk/pubspec.lock index 0539950c64..395abe99e6 100644 --- a/frontend/app_flowy/packages/flowy_sdk/pubspec.lock +++ b/frontend/app_flowy/packages/flowy_sdk/pubspec.lock @@ -168,7 +168,7 @@ packages: name: dartz url: "https://pub.dartlang.org" source: hosted - version: "0.10.0-nullsafety.2" + version: "0.10.1" fake_async: dependency: transitive description: @@ -208,7 +208,7 @@ packages: name: flutter_lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -290,7 +290,7 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.1" logger: dependency: "direct main" description: @@ -500,5 +500,5 @@ packages: source: hosted version: "3.1.0" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index f280d3b4ca..8d15f02e4b 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -35,7 +35,7 @@ packages: path: "packages/appflowy_editor" relative: true source: path - version: "0.0.6" + version: "0.0.7" appflowy_popover: dependency: "direct main" description: @@ -212,12 +212,12 @@ packages: source: hosted version: "1.2.4" connectivity_plus_platform_interface: - dependency: transitive + dependency: "direct main" description: name: connectivity_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.3" connectivity_plus_web: dependency: transitive description: @@ -246,13 +246,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" - cross_file: - dependency: transitive - description: - name: cross_file - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.3+1" crypto: dependency: transitive description: @@ -393,6 +386,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.2" + file_picker: + dependency: "direct main" + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "4.6.1" fixnum: dependency: "direct main" description: @@ -459,34 +459,6 @@ packages: description: flutter source: sdk version: "0.0.0" - flutter_inappwebview: - dependency: transitive - description: - name: flutter_inappwebview - url: "https://pub.dartlang.org" - source: hosted - version: "5.4.3+7" - flutter_keyboard_visibility: - dependency: transitive - description: - name: flutter_keyboard_visibility - url: "https://pub.dartlang.org" - source: hosted - version: "5.2.0" - flutter_keyboard_visibility_platform_interface: - dependency: transitive - description: - name: flutter_keyboard_visibility_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" - flutter_keyboard_visibility_web: - dependency: transitive - description: - name: flutter_keyboard_visibility_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" flutter_lints: dependency: "direct dev" description: @@ -506,15 +478,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.6" - flutter_quill: - dependency: "direct main" - description: - path: "." - ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" - resolved-ref: "306fd78b7a134abdde0fed6be67f59e8a6068509" - url: "https://github.com/appflowy/flutter-quill.git" - source: git - version: "2.0.13" flutter_svg: dependency: transitive description: @@ -572,13 +535,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "7.2.0" - gettext_parser: - dependency: transitive - description: - name: gettext_parser - url: "https://pub.dartlang.org" - source: hosted - version: "0.2.0" glob: dependency: transitive description: @@ -586,6 +542,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.2" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" graphs: dependency: transitive description: @@ -628,48 +591,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.1" - i18n_extension: - dependency: transitive - description: - name: i18n_extension - url: "https://pub.dartlang.org" - source: hosted - version: "4.2.1" - image_picker: - dependency: transitive - description: - name: image_picker - url: "https://pub.dartlang.org" - source: hosted - version: "0.8.5+3" - image_picker_android: - dependency: transitive - description: - name: image_picker_android - url: "https://pub.dartlang.org" - source: hosted - version: "0.8.4+13" - image_picker_for_web: - dependency: transitive - description: - name: image_picker_for_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.8" - image_picker_ios: - dependency: transitive - description: - name: image_picker_ios - url: "https://pub.dartlang.org" - source: hosted - version: "0.8.5+5" - image_picker_platform_interface: - dependency: transitive - description: - name: image_picker_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "2.5.0" integration_test: dependency: "direct dev" description: flutter @@ -759,6 +680,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.1" matcher: dependency: transitive description: @@ -934,13 +862,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.6" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.1" petitparser: dependency: transitive description: @@ -948,13 +869,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.0.0" - photo_view: - dependency: transitive - description: - name: photo_view - url: "https://pub.dartlang.org" - source: hosted - version: "0.13.0" platform: dependency: transitive description: @@ -1212,13 +1126,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" - sprintf: - dependency: transitive - description: - name: sprintf - url: "https://pub.dartlang.org" - source: hosted - version: "6.0.0" stack_trace: dependency: transitive description: @@ -1247,13 +1154,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" - string_validator: - dependency: transitive - description: - name: string_validator - url: "https://pub.dartlang.org" - source: hosted - version: "0.3.0" styled_widget: dependency: "direct main" description: @@ -1422,41 +1322,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.2" - video_player: - dependency: transitive - description: - name: video_player - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.2" - video_player_android: - dependency: transitive - description: - name: video_player_android - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.4" - video_player_avfoundation: - dependency: transitive - description: - name: video_player_avfoundation - url: "https://pub.dartlang.org" - source: hosted - version: "2.3.4" - video_player_platform_interface: - dependency: transitive - description: - name: video_player_platform_interface - url: "https://pub.dartlang.org" - source: hosted - version: "5.1.2" - video_player_web: - dependency: transitive - description: - name: video_player_web - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.10" vm_service: dependency: transitive description: @@ -1529,13 +1394,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.1" - youtube_player_flutter: - dependency: transitive - description: - name: youtube_player_flutter - url: "https://pub.dartlang.org" - source: hosted - version: "8.1.0" sdks: dart: ">=2.17.0 <3.0.0" flutter: ">=3.0.0" diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index 302d8b920e..3a389c02c6 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -43,10 +43,6 @@ dependencies: path: packages/appflowy_editor appflowy_popover: path: packages/appflowy_popover - flutter_quill: - git: - url: https://github.com/appflowy/flutter-quill.git - ref: 306fd78b7a134abdde0fed6be67f59e8a6068509 # third party packages intl: ^0.17.0 @@ -72,6 +68,7 @@ dependencies: # file_picker: ^4.2.1 clipboard: ^0.1.3 connectivity_plus: ^2.3.6+1 + connectivity_plus_platform_interface: ^1.2.2 easy_localization: ^3.0.0 textfield_tags: ^2.0.0 # The following adds the Cupertino Icons font to your application. @@ -91,6 +88,8 @@ dependencies: bloc: ^8.1.0 textstyle_extensions: "2.0.0-nullsafety" shared_preferences: ^2.0.15 + google_fonts: ^3.0.1 + file_picker: <=5.0.0 dev_dependencies: flutter_lints: ^2.0.1 @@ -129,6 +128,26 @@ flutter: - family: FlowyIconData fonts: - asset: assets/fonts/FlowyIconData.ttf + - family: Poppins + fonts: + - asset: assets/google_fonts/Poppins/Poppins-ExtraLight.ttf + weight: 100 + - asset: assets/google_fonts/Poppins/Poppins-Thin.ttf + weight: 200 + - asset: assets/google_fonts/Poppins/Poppins-Light.ttf + weight: 300 + - asset: assets/google_fonts/Poppins/Poppins-Regular.ttf + weight: 400 + - asset: assets/google_fonts/Poppins/Poppins-Medium.ttf + weight: 500 + - asset: assets/google_fonts/Poppins/Poppins-SemiBold.ttf + weight: 600 + - asset: assets/google_fonts/Poppins/Poppins-Bold.ttf + weight: 700 + - asset: assets/google_fonts/Poppins/Poppins-Black.ttf + weight: 800 + - asset: assets/google_fonts/Poppins/Poppins-ExtraBold.ttf + weight: 900 # To add assets to your application, add an assets section, like this: assets: diff --git a/frontend/app_flowy/test/bloc_test/app_setting_test/appearance_test.dart b/frontend/app_flowy/test/bloc_test/app_setting_test/appearance_test.dart new file mode 100644 index 0000000000..72cbb0957f --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/app_setting_test/appearance_test.dart @@ -0,0 +1,54 @@ +import 'package:app_flowy/user/application/user_settings_service.dart'; +import 'package:app_flowy/workspace/application/appearance.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flowy_sdk/protobuf/flowy-user/user_setting.pb.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../util.dart'; + +void main() { + // ignore: unused_local_variable + late AppFlowyUnitTest context; + setUpAll(() async { + context = await AppFlowyUnitTest.ensureInitialized(); + }); + + group('$AppearanceSettingsCubit', () { + late AppearanceSettingsPB appearanceSetting; + setUp(() async { + appearanceSetting = await SettingsFFIService().getAppearanceSetting(); + await blocResponseFuture(); + }); + + blocTest( + 'default theme', + build: () => AppearanceSettingsCubit(appearanceSetting), + verify: (bloc) { + expect(bloc.state.theme.brightness, Brightness.light); + }, + ); + + blocTest( + 'save key/value', + build: () => AppearanceSettingsCubit(appearanceSetting), + act: (bloc) { + bloc.setKeyValue("123", "456"); + }, + verify: (bloc) { + expect(bloc.getValue("123"), "456"); + }, + ); + + blocTest( + 'remove key/value', + build: () => AppearanceSettingsCubit(appearanceSetting), + act: (bloc) { + bloc.setKeyValue("123", null); + }, + verify: (bloc) { + expect(bloc.getValue("123"), null); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/create_card_test.dart b/frontend/app_flowy/test/bloc_test/board_test/create_card_test.dart new file mode 100644 index 0000000000..c03621bf7a --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/create_card_test.dart @@ -0,0 +1,45 @@ +import 'package:app_flowy/plugins/board/application/board_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'util.dart'; + +void main() { + late AppFlowyBoardTest boardTest; + + setUpAll(() async { + boardTest = await AppFlowyBoardTest.ensureInitialized(); + }); + + group('$BoardBloc', () { + late BoardBloc boardBloc; + late String groupId; + late BoardTestContext context; + + setUp(() async { + context = await boardTest.createTestBoard(); + boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + await boardResponseFuture(); + groupId = boardBloc.state.groupIds.first; + + // the group at index 0 is the 'No status' group; + assert(boardBloc.groupControllers[groupId]!.group.rows.isEmpty); + assert(boardBloc.state.groupIds.length == 4); + }); + + blocTest( + "create card", + build: () => boardBloc, + act: (bloc) async { + boardBloc.add(BoardEvent.createBottomRow(boardBloc.state.groupIds[0])); + }, + wait: boardResponseDuration(), + verify: (bloc) { + // + + assert(bloc.groupControllers[groupId]!.group.rows.length == 1); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/create_or_edit_field_test.dart b/frontend/app_flowy/test/bloc_test/board_test/create_or_edit_field_test.dart new file mode 100644 index 0000000000..ff7c101506 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/create_or_edit_field_test.dart @@ -0,0 +1,127 @@ +import 'package:app_flowy/plugins/board/application/board_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'util.dart'; + +void main() { + late AppFlowyBoardTest boardTest; + + setUpAll(() async { + boardTest = await AppFlowyBoardTest.ensureInitialized(); + }); + + group('The grouped field is not changed after editing a field:', () { + late BoardBloc boardBloc; + late FieldEditorBloc editorBloc; + late BoardTestContext context; + setUpAll(() async { + context = await boardTest.createTestBoard(); + }); + + setUp(() async { + boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + + final fieldContext = context.singleSelectFieldContext(); + final loader = FieldTypeOptionLoader( + gridId: context.gridView.id, + field: fieldContext.field, + ); + + editorBloc = FieldEditorBloc( + gridId: context.gridView.id, + fieldName: fieldContext.name, + isGroupField: fieldContext.isGroupField, + loader: loader, + )..add(const FieldEditorEvent.initial()); + + await boardResponseFuture(); + }); + + blocTest( + "initial", + build: () => boardBloc, + wait: boardResponseDuration(), + verify: (bloc) { + assert(bloc.groupControllers.values.length == 4); + assert(context.fieldContexts.length == 2); + }, + ); + + blocTest( + "edit a field", + build: () => editorBloc, + act: (bloc) async { + editorBloc.add(const FieldEditorEvent.updateName('Hello world')); + }, + wait: boardResponseDuration(), + verify: (bloc) { + bloc.state.field.fold( + () => throw Exception("The field should not be none"), + (field) { + assert(field.name == 'Hello world'); + }, + ); + }, + ); + + blocTest( + "assert the groups were not changed", + build: () => boardBloc, + wait: boardResponseDuration(), + verify: (bloc) { + assert(bloc.groupControllers.values.length == 4, + "Expected 4, but receive ${bloc.groupControllers.values.length}"); + + assert(context.fieldContexts.length == 2, + "Expected 2, but receive ${context.fieldContexts.length}"); + }, + ); + }); + group('The grouped field is not changed after creating a new field:', () { + late BoardBloc boardBloc; + late BoardTestContext context; + setUpAll(() async { + context = await boardTest.createTestBoard(); + }); + + setUp(() async { + boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + await boardResponseFuture(); + }); + + blocTest( + "initial", + build: () => boardBloc, + wait: boardResponseDuration(), + verify: (bloc) { + assert(bloc.groupControllers.values.length == 4); + assert(context.fieldContexts.length == 2); + }, + ); + + test('create a field', () async { + await context.createField(FieldType.Checkbox); + await boardResponseFuture(); + final checkboxField = context.fieldContexts.last.field; + assert(checkboxField.fieldType == FieldType.Checkbox); + }); + + blocTest( + "assert the groups were not changed", + build: () => boardBloc, + wait: boardResponseDuration(), + verify: (bloc) { + assert(bloc.groupControllers.values.length == 4, + "Expected 4, but receive ${bloc.groupControllers.values.length}"); + + assert(context.fieldContexts.length == 3, + "Expected 3, but receive ${context.fieldContexts.length}"); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/group_by_checkbox_field_test.dart b/frontend/app_flowy/test/bloc_test/board_test/group_by_checkbox_field_test.dart new file mode 100644 index 0000000000..b6d29587df --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/group_by_checkbox_field_test.dart @@ -0,0 +1,45 @@ +import 'package:app_flowy/plugins/board/application/board_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/setting/group_bloc.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'util.dart'; + +void main() { + late AppFlowyBoardTest boardTest; + + setUpAll(() async { + boardTest = await AppFlowyBoardTest.ensureInitialized(); + }); + + // Group by checkbox field + test('group by checkbox field test', () async { + final context = await boardTest.createTestBoard(); + final boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + await boardResponseFuture(); + + // assert the initial values + assert(boardBloc.groupControllers.values.length == 4); + assert(context.fieldContexts.length == 2); + + // create checkbox field + await context.createField(FieldType.Checkbox); + await boardResponseFuture(); + assert(context.fieldContexts.length == 3); + + // set group by checkbox + final checkboxField = context.fieldContexts.last.field; + final gridGroupBloc = GridGroupBloc( + viewId: context.gridView.id, + fieldController: context.fieldController, + ); + gridGroupBloc.add(GridGroupEvent.setGroupByField( + checkboxField.id, + checkboxField.fieldType, + )); + await boardResponseFuture(); + + assert(boardBloc.groupControllers.values.length == 2); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/group_by_multi_select_field_test.dart b/frontend/app_flowy/test/bloc_test/board_test/group_by_multi_select_field_test.dart new file mode 100644 index 0000000000..39bd773722 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/group_by_multi_select_field_test.dart @@ -0,0 +1,95 @@ +import 'package:app_flowy/plugins/board/application/board_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/setting/group_bloc.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'util.dart'; + +void main() { + late AppFlowyBoardTest boardTest; + + setUpAll(() async { + boardTest = await AppFlowyBoardTest.ensureInitialized(); + }); + + test('group by multi select with no options test', () async { + final context = await boardTest.createTestBoard(); + + // create multi-select field + await context.createField(FieldType.MultiSelect); + await boardResponseFuture(); + assert(context.fieldContexts.length == 3); + final multiSelectField = context.fieldContexts.last.field; + + // set grouped by the new multi-select field" + final gridGroupBloc = GridGroupBloc( + viewId: context.gridView.id, + fieldController: context.fieldController, + ); + gridGroupBloc.add(GridGroupEvent.setGroupByField( + multiSelectField.id, + multiSelectField.fieldType, + )); + await boardResponseFuture(); + + //assert only have the 'No status' group + final boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + await boardResponseFuture(); + assert(boardBloc.groupControllers.values.length == 1, + "Expected 1, but receive ${boardBloc.groupControllers.values.length}"); + final expectedGroupName = "No ${multiSelectField.name}"; + assert( + boardBloc.groupControllers.values.first.group.desc == expectedGroupName, + "Expected $expectedGroupName, but receive ${boardBloc.groupControllers.values.first.group.desc}"); + }); + + test('group by multi select with no options test', () async { + final context = await boardTest.createTestBoard(); + + // create multi-select field + await context.createField(FieldType.MultiSelect); + await boardResponseFuture(); + assert(context.fieldContexts.length == 3); + final multiSelectField = context.fieldContexts.last.field; + + // Create options + final cellController = await context.makeCellController(multiSelectField.id) + as GridSelectOptionCellController; + + final multiSelectOptionBloc = + SelectOptionCellEditorBloc(cellController: cellController); + multiSelectOptionBloc.add(const SelectOptionEditorEvent.initial()); + await boardResponseFuture(); + multiSelectOptionBloc.add(const SelectOptionEditorEvent.newOption("A")); + await boardResponseFuture(); + multiSelectOptionBloc.add(const SelectOptionEditorEvent.newOption("B")); + await boardResponseFuture(); + + // set grouped by the new multi-select field" + final gridGroupBloc = GridGroupBloc( + viewId: context.gridView.id, + fieldController: context.fieldController, + ); + gridGroupBloc.add(GridGroupEvent.setGroupByField( + multiSelectField.id, + multiSelectField.fieldType, + )); + await boardResponseFuture(); + + // assert there are only three group + final boardBloc = BoardBloc(view: context.gridView) + ..add(const BoardEvent.initial()); + await boardResponseFuture(); + assert(boardBloc.groupControllers.values.length == 3, + "Expected 3, but receive ${boardBloc.groupControllers.values.length}"); + + final groups = + boardBloc.groupControllers.values.map((e) => e.group).toList(); + assert(groups[0].desc == "No ${multiSelectField.name}"); + assert(groups[1].desc == "B"); + assert(groups[2].desc == "A"); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/group_by_unsupport_field_test.dart b/frontend/app_flowy/test/bloc_test/board_test/group_by_unsupport_field_test.dart new file mode 100644 index 0000000000..660734d8c8 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/group_by_unsupport_field_test.dart @@ -0,0 +1,51 @@ +import 'package:app_flowy/plugins/board/application/board_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'util.dart'; + +void main() { + late AppFlowyBoardTest boardTest; + late FieldEditorBloc editorBloc; + late BoardTestContext context; + + setUpAll(() async { + boardTest = await AppFlowyBoardTest.ensureInitialized(); + context = await boardTest.createTestBoard(); + final fieldContext = context.singleSelectFieldContext(); + editorBloc = context.createFieldEditor( + fieldContext: fieldContext, + )..add(const FieldEditorEvent.initial()); + + await boardResponseFuture(); + }); + + group('Group with not support grouping field:', () { + blocTest( + "switch to text field", + build: () => editorBloc, + wait: boardResponseDuration(), + act: (bloc) async { + bloc.add(const FieldEditorEvent.switchToField(FieldType.RichText)); + }, + verify: (bloc) { + bloc.state.field.fold( + () => throw Exception(), + (field) => field.fieldType == FieldType.RichText, + ); + }, + ); + blocTest( + 'assert the number of groups is 1', + build: () => + BoardBloc(view: context.gridView)..add(const BoardEvent.initial()), + wait: boardResponseDuration(), + verify: (bloc) { + assert(bloc.groupControllers.values.length == 1, + "Expected 1, but receive ${bloc.groupControllers.values.length}"); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/board_test/util.dart b/frontend/app_flowy/test/bloc_test/board_test/util.dart new file mode 100644 index 0000000000..32ae041fb2 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/board_test/util.dart @@ -0,0 +1,171 @@ +import 'dart:collection'; + +import 'package:app_flowy/plugins/board/application/board_data_controller.dart'; +import 'package:app_flowy/plugins/board/board.dart'; +import 'package:app_flowy/plugins/grid/application/block/block_cache.dart'; +import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; +import 'package:app_flowy/plugins/grid/application/row/row_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; +import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'; +import 'package:app_flowy/workspace/application/app/app_service.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; + +import '../../util.dart'; +import '../grid_test/util.dart'; + +class AppFlowyBoardTest { + final AppFlowyUnitTest unitTest; + + AppFlowyBoardTest({required this.unitTest}); + + static Future ensureInitialized() async { + final inner = await AppFlowyUnitTest.ensureInitialized(); + return AppFlowyBoardTest(unitTest: inner); + } + + Future createTestBoard() async { + final app = await unitTest.createTestApp(); + final builder = BoardPluginBuilder(); + return AppService() + .createView( + appId: app.id, + name: "Test Board", + dataFormatType: builder.dataFormatType, + pluginType: builder.pluginType, + layoutType: builder.layoutType!, + ) + .then((result) { + return result.fold( + (view) async { + final context = + BoardTestContext(view, BoardDataController(view: view)); + final result = await context._boardDataController.openGrid(); + result.fold((l) => null, (r) => throw Exception(r)); + return context; + }, + (error) { + throw Exception(); + }, + ); + }); + } +} + +Future boardResponseFuture() { + return Future.delayed(boardResponseDuration(milliseconds: 200)); +} + +Duration boardResponseDuration({int milliseconds = 200}) { + return Duration(milliseconds: milliseconds); +} + +class BoardTestContext { + final ViewPB gridView; + final BoardDataController _boardDataController; + + BoardTestContext(this.gridView, this._boardDataController); + + List get rowInfos { + return _boardDataController.rowInfos; + } + + UnmodifiableMapView get blocks { + return _boardDataController.blocks; + } + + List get fieldContexts => fieldController.fieldContexts; + + GridFieldController get fieldController { + return _boardDataController.fieldController; + } + + FieldEditorBloc createFieldEditor({ + GridFieldContext? fieldContext, + }) { + IFieldTypeOptionLoader loader; + if (fieldContext == null) { + loader = NewFieldTypeOptionLoader(gridId: gridView.id); + } else { + loader = + FieldTypeOptionLoader(gridId: gridView.id, field: fieldContext.field); + } + + final editorBloc = FieldEditorBloc( + fieldName: fieldContext?.name ?? '', + isGroupField: fieldContext?.isGroupField ?? false, + loader: loader, + gridId: gridView.id, + ); + return editorBloc; + } + + Future makeCellController(String fieldId) async { + final builder = await makeCellControllerBuilder(fieldId); + return builder.build(); + } + + Future makeCellControllerBuilder( + String fieldId, + ) async { + final RowInfo rowInfo = rowInfos.last; + final blockCache = blocks[rowInfo.rowPB.blockId]; + final rowCache = blockCache?.rowCache; + + final fieldController = _boardDataController.fieldController; + + final rowDataController = GridRowDataController( + rowInfo: rowInfo, + fieldController: fieldController, + rowCache: rowCache!, + ); + + final rowBloc = RowBloc( + rowInfo: rowInfo, + dataController: rowDataController, + )..add(const RowEvent.initial()); + await gridResponseFuture(); + + return GridCellControllerBuilder( + cellId: rowBloc.state.gridCellMap[fieldId]!, + cellCache: rowCache.cellCache, + delegate: rowDataController, + ); + } + + Future createField(FieldType fieldType) async { + final editorBloc = createFieldEditor() + ..add(const FieldEditorEvent.initial()); + await gridResponseFuture(); + editorBloc.add(FieldEditorEvent.switchToField(fieldType)); + await gridResponseFuture(); + return Future(() => editorBloc); + } + + GridFieldContext singleSelectFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.SingleSelect); + return fieldContext; + } + + GridFieldCellContext singleSelectFieldCellContext() { + final field = singleSelectFieldContext().field; + return GridFieldCellContext(gridId: gridView.id, field: field); + } + + GridFieldContext textFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.RichText); + return fieldContext; + } + + GridFieldContext checkboxFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.Checkbox); + return fieldContext; + } +} diff --git a/frontend/app_flowy/test/bloc_test/grid_test/field_edit_bloc_test.dart b/frontend/app_flowy/test/bloc_test/grid_test/field_edit_bloc_test.dart new file mode 100644 index 0000000000..108a1837cd --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/grid_test/field_edit_bloc_test.dart @@ -0,0 +1,102 @@ +import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; +import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'util.dart'; + +Future createEditorBloc(AppFlowyGridTest gridTest) async { + final context = await gridTest.createTestGrid(); + final fieldContext = context.singleSelectFieldContext(); + final loader = FieldTypeOptionLoader( + gridId: context.gridView.id, + field: fieldContext.field, + ); + + return FieldEditorBloc( + gridId: context.gridView.id, + fieldName: fieldContext.name, + isGroupField: fieldContext.isGroupField, + loader: loader, + )..add(const FieldEditorEvent.initial()); +} + +void main() { + late AppFlowyGridTest gridTest; + + setUpAll(() async { + gridTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('$FieldEditorBloc', () { + late FieldEditorBloc editorBloc; + + setUp(() async { + final context = await gridTest.createTestGrid(); + final fieldContext = context.singleSelectFieldContext(); + final loader = FieldTypeOptionLoader( + gridId: context.gridView.id, + field: fieldContext.field, + ); + + editorBloc = FieldEditorBloc( + gridId: context.gridView.id, + fieldName: fieldContext.name, + isGroupField: fieldContext.isGroupField, + loader: loader, + )..add(const FieldEditorEvent.initial()); + + await gridResponseFuture(); + }); + + blocTest( + "rename field", + build: () => editorBloc, + act: (bloc) async { + editorBloc.add(const FieldEditorEvent.updateName('Hello world')); + }, + wait: gridResponseDuration(), + verify: (bloc) { + bloc.state.field.fold( + () => throw Exception("The field should not be none"), + (field) { + assert(field.name == 'Hello world'); + }, + ); + }, + ); + + blocTest( + "switch to text field", + build: () => editorBloc, + act: (bloc) async { + editorBloc + .add(const FieldEditorEvent.switchToField(FieldType.RichText)); + }, + wait: gridResponseDuration(), + verify: (bloc) { + bloc.state.field.fold( + () => throw Exception("The field should not be none"), + (field) { + // The default length of the fields is 3. The length of the fields + // should not change after switching to other field type + // assert(gridTest.fieldContexts.length == 3); + assert(field.fieldType == FieldType.RichText); + }, + ); + }, + ); + + blocTest( + "delete field", + build: () => editorBloc, + act: (bloc) async { + editorBloc.add(const FieldEditorEvent.deleteField()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + // assert(gridTest.fieldContexts.length == 2); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/grid_test/filter_bloc_test.dart b/frontend/app_flowy/test/bloc_test/grid_test/filter_bloc_test.dart new file mode 100644 index 0000000000..5434ba0fee --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/grid_test/filter_bloc_test.dart @@ -0,0 +1,144 @@ +import 'package:app_flowy/plugins/grid/application/filter/filter_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/grid_bloc.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'util.dart'; + +void main() { + late AppFlowyGridTest gridTest; + setUpAll(() async { + gridTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('$GridFilterBloc', () { + late GridTestContext context; + setUp(() async { + context = await gridTest.createTestGrid(); + }); + + blocTest( + "create a text filter", + build: () => GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()), + act: (bloc) async { + final textField = context.textFieldContext(); + bloc.add( + GridFilterEvent.createTextFilter( + fieldId: textField.id, + condition: TextFilterCondition.TextIsEmpty, + content: ""), + ); + }, + wait: const Duration(milliseconds: 300), + verify: (bloc) { + assert(bloc.state.filters.length == 1); + }, + ); + + blocTest( + "delete a text filter", + build: () => GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()), + act: (bloc) async { + final textField = context.textFieldContext(); + bloc.add( + GridFilterEvent.createTextFilter( + fieldId: textField.id, + condition: TextFilterCondition.TextIsEmpty, + content: ""), + ); + await gridResponseFuture(); + final filter = bloc.state.filters.first; + bloc.add( + GridFilterEvent.deleteFilter( + fieldId: textField.id, + filterId: filter.id, + fieldType: textField.fieldType, + ), + ); + }, + wait: const Duration(milliseconds: 300), + verify: (bloc) { + assert(bloc.state.filters.isEmpty); + }, + ); + }); + + test('filter rows with condition: text is empty', () async { + final context = await gridTest.createTestGrid(); + final filterBloc = GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()); + + final gridBloc = GridBloc(view: context.gridView) + ..add(const GridEvent.initial()); + + final textField = context.textFieldContext(); + await gridResponseFuture(); + filterBloc.add( + GridFilterEvent.createTextFilter( + fieldId: textField.id, + condition: TextFilterCondition.TextIsEmpty, + content: ""), + ); + + await gridResponseFuture(); + assert(gridBloc.state.rowInfos.length == 3); + }); + + test('filter rows with condition: text is not empty', () async { + final context = await gridTest.createTestGrid(); + final filterBloc = GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()); + + final textField = context.textFieldContext(); + await gridResponseFuture(); + filterBloc.add( + GridFilterEvent.createTextFilter( + fieldId: textField.id, + condition: TextFilterCondition.TextIsNotEmpty, + content: ""), + ); + await gridResponseFuture(); + assert(context.rowInfos.isEmpty); + }); + + test('filter rows with condition: checkbox uncheck', () async { + final context = await gridTest.createTestGrid(); + final checkboxField = context.checkboxFieldContext(); + final filterBloc = GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()); + final gridBloc = GridBloc(view: context.gridView) + ..add(const GridEvent.initial()); + + await gridResponseFuture(); + filterBloc.add( + GridFilterEvent.createCheckboxFilter( + fieldId: checkboxField.id, + condition: CheckboxFilterCondition.IsUnChecked, + ), + ); + await gridResponseFuture(); + assert(gridBloc.state.rowInfos.length == 3); + }); + + test('filter rows with condition: checkbox check', () async { + final context = await gridTest.createTestGrid(); + final checkboxField = context.checkboxFieldContext(); + final filterBloc = GridFilterBloc(viewId: context.gridView.id) + ..add(const GridFilterEvent.initial()); + final gridBloc = GridBloc(view: context.gridView) + ..add(const GridEvent.initial()); + + await gridResponseFuture(); + filterBloc.add( + GridFilterEvent.createCheckboxFilter( + fieldId: checkboxField.id, + condition: CheckboxFilterCondition.IsChecked, + ), + ); + await gridResponseFuture(); + assert(gridBloc.state.rowInfos.isEmpty); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/grid_test/grid_bloc_test.dart b/frontend/app_flowy/test/bloc_test/grid_test/grid_bloc_test.dart index b2bfa6996f..0afff2cbc6 100644 --- a/frontend/app_flowy/test/bloc_test/grid_test/grid_bloc_test.dart +++ b/frontend/app_flowy/test/bloc_test/grid_test/grid_bloc_test.dart @@ -9,36 +9,36 @@ void main() { gridTest = await AppFlowyGridTest.ensureInitialized(); }); - group('GridBloc', () { + group('Edit Grid:', () { + late GridTestContext context; + setUp(() async { + context = await gridTest.createTestGrid(); + }); + // The initial number of rows is 3 for each grid. blocTest( - "Create row", + "create a row", build: () => - GridBloc(view: gridTest.gridView)..add(const GridEvent.initial()), + GridBloc(view: context.gridView)..add(const GridEvent.initial()), act: (bloc) => bloc.add(const GridEvent.createRow()), wait: const Duration(milliseconds: 300), verify: (bloc) { assert(bloc.state.rowInfos.length == 4); }, ); - }); - group('GridBloc', () { - late GridBloc gridBloc; - setUpAll(() async { - gridBloc = GridBloc(view: gridTest.gridView) - ..add(const GridEvent.initial()); - await gridResponseFuture(); - }); - - // The initial number of rows is three - test('', () async { - assert(gridBloc.state.rowInfos.length == 3); - }); - - test('delete row', () async { - gridBloc.add(GridEvent.deleteRow(gridBloc.state.rowInfos.last)); - await gridResponseFuture(); - assert(gridBloc.state.rowInfos.length == 2); - }); + blocTest( + "delete the last row", + build: () => + GridBloc(view: context.gridView)..add(const GridEvent.initial()), + act: (bloc) async { + await gridResponseFuture(); + bloc.add(GridEvent.deleteRow(bloc.state.rowInfos.last)); + }, + wait: const Duration(milliseconds: 300), + verify: (bloc) { + assert(bloc.state.rowInfos.length == 2, + "Expected 2, but receive ${bloc.state.rowInfos.length}"); + }, + ); }); } diff --git a/frontend/app_flowy/test/bloc_test/grid_test/grid_header_bloc_test.dart b/frontend/app_flowy/test/bloc_test/grid_test/grid_header_bloc_test.dart new file mode 100644 index 0000000000..68273bb6ae --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/grid_test/grid_header_bloc_test.dart @@ -0,0 +1,126 @@ +import 'package:app_flowy/plugins/grid/application/field/field_action_sheet_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/grid_header_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'util.dart'; + +void main() { + late AppFlowyGridTest gridTest; + + setUpAll(() async { + gridTest = await AppFlowyGridTest.ensureInitialized(); + }); + + group('$GridHeaderBloc', () { + late FieldActionSheetBloc actionSheetBloc; + late GridTestContext context; + setUp(() async { + context = await gridTest.createTestGrid(); + actionSheetBloc = FieldActionSheetBloc( + fieldCellContext: context.singleSelectFieldCellContext(), + ); + }); + + blocTest( + "hides property", + build: () { + final bloc = GridHeaderBloc( + gridId: context.gridView.id, + fieldController: context.fieldController, + )..add(const GridHeaderEvent.initial()); + return bloc; + }, + act: (bloc) async { + actionSheetBloc.add(const FieldActionSheetEvent.hideField()); + await Future.delayed(gridResponseDuration()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.fields.length == 2); + }, + ); + + blocTest( + "shows property", + build: () { + final bloc = GridHeaderBloc( + gridId: context.gridView.id, + fieldController: context.fieldController, + )..add(const GridHeaderEvent.initial()); + return bloc; + }, + act: (bloc) async { + actionSheetBloc.add(const FieldActionSheetEvent.hideField()); + await Future.delayed(gridResponseDuration()); + actionSheetBloc.add(const FieldActionSheetEvent.showField()); + await Future.delayed(gridResponseDuration()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.fields.length == 3); + }, + ); + + blocTest( + "duplicate property", + build: () { + final bloc = GridHeaderBloc( + gridId: context.gridView.id, + fieldController: context.fieldController, + )..add(const GridHeaderEvent.initial()); + return bloc; + }, + act: (bloc) async { + actionSheetBloc.add(const FieldActionSheetEvent.duplicateField()); + await Future.delayed(gridResponseDuration()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.fields.length == 4); + }, + ); + + blocTest( + "delete property", + build: () { + final bloc = GridHeaderBloc( + gridId: context.gridView.id, + fieldController: context.fieldController, + )..add(const GridHeaderEvent.initial()); + return bloc; + }, + act: (bloc) async { + actionSheetBloc.add(const FieldActionSheetEvent.deleteField()); + await Future.delayed(gridResponseDuration()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.fields.length == 2); + }, + ); + + blocTest( + "update name", + build: () { + final bloc = GridHeaderBloc( + gridId: context.gridView.id, + fieldController: context.fieldController, + )..add(const GridHeaderEvent.initial()); + return bloc; + }, + act: (bloc) async { + actionSheetBloc + .add(const FieldActionSheetEvent.updateFieldName("Hello world")); + await Future.delayed(gridResponseDuration()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + final field = bloc.state.fields.firstWhere( + (element) => element.id == actionSheetBloc.fieldService.fieldId); + + assert(field.name == "Hello world"); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/grid_test/select_option_bloc_test.dart b/frontend/app_flowy/test/bloc_test/grid_test/select_option_bloc_test.dart index bea51b4960..fef80e2181 100644 --- a/frontend/app_flowy/test/bloc_test/grid_test/select_option_bloc_test.dart +++ b/frontend/app_flowy/test/bloc_test/grid_test/select_option_bloc_test.dart @@ -1,23 +1,51 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/prelude.dart'; +import 'package:dartz/dartz.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:bloc_test/bloc_test.dart'; import 'util.dart'; void main() { - late AppFlowyGridSelectOptionCellTest cellTest; + late AppFlowyGridCellTest cellTest; setUpAll(() async { - cellTest = await AppFlowyGridSelectOptionCellTest.ensureInitialized(); + cellTest = await AppFlowyGridCellTest.ensureInitialized(); }); group('SingleSelectOptionBloc', () { late GridSelectOptionCellController cellController; setUp(() async { - cellController = - await cellTest.makeCellController(FieldType.SingleSelect); + await cellTest.createTestGrid(); + await cellTest.createTestRow(); + cellController = await cellTest.makeCellController( + FieldType.SingleSelect, + ); }); + blocTest( + "delete options", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.newOption("B")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.newOption("C")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.deleteAllOptions()); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.options.isEmpty); + }, + ); + blocTest( "create option", build: () { @@ -25,11 +53,148 @@ void main() { bloc.add(const SelectOptionEditorEvent.initial()); return bloc; }, - act: (bloc) => bloc.add(const SelectOptionEditorEvent.newOption("A")), + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + }, + wait: gridResponseDuration(), + verify: (bloc) { + expect(bloc.state.options.length, 1); + expect(bloc.state.options[0].name, "A"); + }, + ); + + blocTest( + "delete option", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + bloc.add(SelectOptionEditorEvent.deleteOption(bloc.state.options[0])); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.options.isEmpty); + }, + ); + + blocTest( + "update option", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + SelectOptionPB optionUpdate = bloc.state.options[0] + ..color = SelectOptionColorPB.Aqua + ..name = "B"; + bloc.add(SelectOptionEditorEvent.updateOption(optionUpdate)); + }, wait: gridResponseDuration(), verify: (bloc) { assert(bloc.state.options.length == 1); - assert(bloc.state.options[0].name == "A"); + expect(bloc.state.options[0].color, SelectOptionColorPB.Aqua); + expect(bloc.state.options[0].name, "B"); + }, + ); + + blocTest( + "select/unselect option", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + expect(bloc.state.selectedOptions.length, 1); + final optionId = bloc.state.options[0].id; + bloc.add(SelectOptionEditorEvent.unSelectOption(optionId)); + await Future.delayed(gridResponseDuration()); + assert(bloc.state.selectedOptions.isEmpty); + bloc.add(SelectOptionEditorEvent.selectOption(optionId)); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.selectedOptions.length == 1); + expect(bloc.state.selectedOptions[0].name, "A"); + }, + ); + + blocTest( + "select an option or create one", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.trySelectOption("B")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.trySelectOption("A")); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.selectedOptions.length == 1); + assert(bloc.state.options.length == 2); + expect(bloc.state.selectedOptions[0].name, "A"); + }, + ); + + blocTest( + "select multiple options", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("A")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.newOption("B")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.selectMultipleOptions( + ["A", "B", "C"], "x")); + }, + wait: gridResponseDuration(), + verify: (bloc) { + assert(bloc.state.selectedOptions.length == 1); + expect(bloc.state.selectedOptions[0].name, "A"); + expect(bloc.state.filter, const Some("x")); + }, + ); + + blocTest( + "filter options", + build: () { + final bloc = SelectOptionCellEditorBloc(cellController: cellController); + bloc.add(const SelectOptionEditorEvent.initial()); + return bloc; + }, + act: (bloc) async { + bloc.add(const SelectOptionEditorEvent.newOption("abcd")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.newOption("aaaa")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.newOption("defg")); + await Future.delayed(gridResponseDuration()); + bloc.add(const SelectOptionEditorEvent.filterOption("a")); + }, + wait: gridResponseDuration(), + verify: (bloc) { + expect(bloc.state.options.length, 2); + expect(bloc.state.allOptions.length, 3); + expect(bloc.state.createOption, const Some("a")); + expect(bloc.state.filter, const Some("a")); }, ); }); diff --git a/frontend/app_flowy/test/bloc_test/grid_test/util.dart b/frontend/app_flowy/test/bloc_test/grid_test/util.dart index e81028dedc..d0c13471d0 100644 --- a/frontend/app_flowy/test/bloc_test/grid_test/util.dart +++ b/frontend/app_flowy/test/bloc_test/grid_test/util.dart @@ -1,4 +1,10 @@ +import 'dart:collection'; +import 'package:app_flowy/plugins/grid/application/block/block_cache.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_controller.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart'; +import 'package:app_flowy/plugins/grid/application/field/field_service.dart'; +import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart'; import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart'; import 'package:app_flowy/plugins/grid/application/row/row_bloc.dart'; import 'package:app_flowy/plugins/grid/application/row/row_cache.dart'; @@ -10,94 +16,66 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart'; import '../../util.dart'; -/// Create a empty Grid for test -class AppFlowyGridTest { - // ignore: unused_field - final AppFlowyUnitTest _inner; - late ViewPB gridView; - AppFlowyGridTest(AppFlowyUnitTest unitTest) : _inner = unitTest; +class GridTestContext { + final ViewPB gridView; + final GridDataController _gridDataController; - static Future ensureInitialized() async { - final inner = await AppFlowyUnitTest.ensureInitialized(); - final test = AppFlowyGridTest(inner); - await test._createTestGrid(); - return test; + GridTestContext(this.gridView, this._gridDataController); + + List get rowInfos { + return _gridDataController.rowInfos; } - Future _createTestGrid() async { - final app = await _inner.createTestApp(); - final builder = GridPluginBuilder(); - final result = await AppService().createView( - appId: app.id, - name: "Test Grid", - dataType: builder.dataType, - pluginType: builder.pluginType, - layoutType: builder.layoutType!, + UnmodifiableMapView get blocks { + return _gridDataController.blocks; + } + + List get fieldContexts => fieldController.fieldContexts; + + GridFieldController get fieldController { + return _gridDataController.fieldController; + } + + Future createRow() async { + return _gridDataController.createRow(); + } + + FieldEditorBloc createFieldEditor({ + GridFieldContext? fieldContext, + }) { + IFieldTypeOptionLoader loader; + if (fieldContext == null) { + loader = NewFieldTypeOptionLoader(gridId: gridView.id); + } else { + loader = + FieldTypeOptionLoader(gridId: gridView.id, field: fieldContext.field); + } + + final editorBloc = FieldEditorBloc( + fieldName: fieldContext?.name ?? '', + isGroupField: fieldContext?.isGroupField ?? false, + loader: loader, + gridId: gridView.id, ); - result.fold( - (view) => gridView = view, - (error) {}, - ); - } -} - -class AppFlowyGridSelectOptionCellTest { - final AppFlowyGridCellTest _cellTest; - - AppFlowyGridSelectOptionCellTest(AppFlowyGridCellTest cellTest) - : _cellTest = cellTest; - - static Future ensureInitialized() async { - final cellTest = await AppFlowyGridCellTest.ensureInitialized(); - final test = AppFlowyGridSelectOptionCellTest(cellTest); - return test; + return editorBloc; } - /// For the moment, just edit the first row of the grid. - Future makeCellController( - FieldType fieldType) async { - assert(fieldType == FieldType.SingleSelect || - fieldType == FieldType.MultiSelect); - - final fieldContexts = - _cellTest._dataController.fieldController.fieldContexts; - final field = - fieldContexts.firstWhere((element) => element.fieldType == fieldType); - final builder = await _cellTest.cellControllerBuilder(0, field.id); - final cellController = builder.build() as GridSelectOptionCellController; - return cellController; - } -} - -class AppFlowyGridCellTest { - // ignore: unused_field - final AppFlowyGridTest _gridTest; - final GridDataController _dataController; - AppFlowyGridCellTest(AppFlowyGridTest gridTest) - : _gridTest = gridTest, - _dataController = GridDataController(view: gridTest.gridView); - - static Future ensureInitialized() async { - final gridTest = await AppFlowyGridTest.ensureInitialized(); - final test = AppFlowyGridCellTest(gridTest); - await test._loadGridData(); - return test; + Future makeCellController(String fieldId) async { + final builder = await makeCellControllerBuilder(fieldId); + return builder.build(); } - Future _loadGridData() async { - final result = await _dataController.loadData(); - result.fold((l) => null, (r) => throw Exception(r)); - } - - Future cellControllerBuilder( - int rowIndex, String fieldId) async { - final RowInfo rowInfo = _dataController.rowInfos[rowIndex]; - final blockCache = _dataController.blocks[rowInfo.rowPB.blockId]; + Future makeCellControllerBuilder( + String fieldId, + ) async { + final RowInfo rowInfo = rowInfos.last; + final blockCache = blocks[rowInfo.rowPB.blockId]; final rowCache = blockCache?.rowCache; + final fieldController = _gridDataController.fieldController; final rowDataController = GridRowDataController( rowInfo: rowInfo, - fieldController: _dataController.fieldController, + fieldController: fieldController, rowCache: rowCache!, ); @@ -105,7 +83,7 @@ class AppFlowyGridCellTest { rowInfo: rowInfo, dataController: rowDataController, )..add(const RowEvent.initial()); - await gridResponseFuture(milliseconds: 300); + await gridResponseFuture(); return GridCellControllerBuilder( cellId: rowBloc.state.gridCellMap[fieldId]!, @@ -113,9 +91,114 @@ class AppFlowyGridCellTest { delegate: rowDataController, ); } + + Future createField(FieldType fieldType) async { + final editorBloc = createFieldEditor() + ..add(const FieldEditorEvent.initial()); + await gridResponseFuture(); + editorBloc.add(FieldEditorEvent.switchToField(fieldType)); + await gridResponseFuture(); + return Future(() => editorBloc); + } + + GridFieldContext singleSelectFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.SingleSelect); + return fieldContext; + } + + GridFieldCellContext singleSelectFieldCellContext() { + final field = singleSelectFieldContext().field; + return GridFieldCellContext(gridId: gridView.id, field: field); + } + + GridFieldContext textFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.RichText); + return fieldContext; + } + + GridFieldContext checkboxFieldContext() { + final fieldContext = fieldContexts + .firstWhere((element) => element.fieldType == FieldType.Checkbox); + return fieldContext; + } } -Future gridResponseFuture({int milliseconds = 200}) { +/// Create a empty Grid for test +class AppFlowyGridTest { + final AppFlowyUnitTest unitTest; + + AppFlowyGridTest({required this.unitTest}); + + static Future ensureInitialized() async { + final inner = await AppFlowyUnitTest.ensureInitialized(); + return AppFlowyGridTest(unitTest: inner); + } + + Future createTestGrid() async { + final app = await unitTest.createTestApp(); + final builder = GridPluginBuilder(); + final context = await AppService() + .createView( + appId: app.id, + name: "Test Grid", + dataFormatType: builder.dataFormatType, + pluginType: builder.pluginType, + layoutType: builder.layoutType!, + ) + .then((result) { + return result.fold( + (view) async { + final context = GridTestContext(view, GridDataController(view: view)); + final result = await context._gridDataController.openGrid(); + result.fold((l) => null, (r) => throw Exception(r)); + return context; + }, + (error) { + throw Exception(); + }, + ); + }); + + return context; + } +} + +/// Create a new Grid for cell test +class AppFlowyGridCellTest { + late GridTestContext context; + final AppFlowyGridTest gridTest; + AppFlowyGridCellTest({required this.gridTest}); + + static Future ensureInitialized() async { + final gridTest = await AppFlowyGridTest.ensureInitialized(); + return AppFlowyGridCellTest(gridTest: gridTest); + } + + Future createTestGrid() async { + context = await gridTest.createTestGrid(); + } + + Future createTestRow() async { + await context.createRow(); + } + + Future makeCellController( + FieldType fieldType) async { + assert(fieldType == FieldType.SingleSelect || + fieldType == FieldType.MultiSelect); + + final fieldContexts = context.fieldContexts; + final field = + fieldContexts.firstWhere((element) => element.fieldType == fieldType); + final cellController = await context.makeCellController(field.id) + as GridSelectOptionCellController; + return cellController; + } +} + +Future gridResponseFuture({int milliseconds = 500}) { return Future.delayed(gridResponseDuration(milliseconds: milliseconds)); } diff --git a/frontend/app_flowy/test/bloc_test/home_test/app_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/app_bloc_test.dart new file mode 100644 index 0000000000..5779304577 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/home_test/app_bloc_test.dart @@ -0,0 +1,165 @@ +import 'package:app_flowy/plugins/document/application/doc_bloc.dart'; +import 'package:app_flowy/plugins/document/document.dart'; +import 'package:app_flowy/plugins/grid/grid.dart'; +import 'package:app_flowy/workspace/application/app/app_bloc.dart'; +import 'package:app_flowy/workspace/application/menu/menu_view_section_bloc.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../util.dart'; + +void main() { + late AppFlowyUnitTest testContext; + setUpAll(() async { + testContext = await AppFlowyUnitTest.ensureInitialized(); + }); + + test('rename app test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(const AppEvent.rename('Hello world')); + await blocResponseFuture(); + + assert(bloc.state.app.name == 'Hello world'); + }); + + test('delete ap test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(const AppEvent.delete()); + await blocResponseFuture(); + + final apps = await testContext.loadApps(); + assert(apps.where((element) => element.id == app.id).isEmpty); + }); + + test('create documents in order', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(AppEvent.createView("1", DocumentPluginBuilder())); + await blocResponseFuture(); + bloc.add(AppEvent.createView("2", DocumentPluginBuilder())); + await blocResponseFuture(); + bloc.add(AppEvent.createView("3", DocumentPluginBuilder())); + await blocResponseFuture(); + + assert(bloc.state.views[0].name == '1'); + assert(bloc.state.views[1].name == '2'); + assert(bloc.state.views[2].name == '3'); + }); + + test('reorder documents test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(AppEvent.createView("1", DocumentPluginBuilder())); + await blocResponseFuture(); + bloc.add(AppEvent.createView("2", DocumentPluginBuilder())); + await blocResponseFuture(); + bloc.add(AppEvent.createView("3", DocumentPluginBuilder())); + await blocResponseFuture(); + + final appViewData = AppViewDataContext(appId: app.id); + appViewData.views = bloc.state.views; + final viewSectionBloc = ViewSectionBloc( + appViewData: appViewData, + )..add(const ViewSectionEvent.initial()); + await blocResponseFuture(); + + viewSectionBloc.add(const ViewSectionEvent.moveView(0, 2)); + await blocResponseFuture(); + + assert(bloc.state.views[0].name == '2'); + assert(bloc.state.views[1].name == '3'); + assert(bloc.state.views[2].name == '1'); + }); + + test('open latest view test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + assert( + bloc.state.latestCreatedView == null, + "assert initial latest create view is null after initialize", + ); + + bloc.add(AppEvent.createView("1", DocumentPluginBuilder())); + await blocResponseFuture(); + assert( + bloc.state.latestCreatedView!.id == bloc.state.views.last.id, + "create a view and assert the latest create view is this view", + ); + + bloc.add(AppEvent.createView("2", DocumentPluginBuilder())); + await blocResponseFuture(); + assert( + bloc.state.latestCreatedView!.id == bloc.state.views.last.id, + "create a view and assert the latest create view is this view", + ); + }); + + test('open latest documents test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(AppEvent.createView("document 1", DocumentPluginBuilder())); + await blocResponseFuture(); + final document1 = bloc.state.latestCreatedView; + assert(document1!.name == "document 1"); + + bloc.add(AppEvent.createView("document 2", DocumentPluginBuilder())); + await blocResponseFuture(); + final document2 = bloc.state.latestCreatedView; + assert(document2!.name == "document 2"); + + // Open document 1 + // ignore: unused_local_variable + final documentBloc = DocumentBloc(view: document1!) + ..add(const DocumentEvent.initial()); + await blocResponseFuture(); + + final workspaceSetting = await FolderEventReadCurrentWorkspace() + .send() + .then((result) => result.fold((l) => l, (r) => throw Exception())); + workspaceSetting.latestView.id == document1.id; + }); + + test('open latest grid test', () async { + final app = await testContext.createTestApp(); + final bloc = AppBloc(app: app)..add(const AppEvent.initial()); + await blocResponseFuture(); + + bloc.add(AppEvent.createView("grid 1", GridPluginBuilder())); + await blocResponseFuture(); + final grid1 = bloc.state.latestCreatedView; + assert(grid1!.name == "grid 1"); + + bloc.add(AppEvent.createView("grid 2", GridPluginBuilder())); + await blocResponseFuture(); + final grid2 = bloc.state.latestCreatedView; + assert(grid2!.name == "grid 2"); + + var workspaceSetting = await FolderEventReadCurrentWorkspace() + .send() + .then((result) => result.fold((l) => l, (r) => throw Exception())); + workspaceSetting.latestView.id == grid1!.id; + + // Open grid 1 + // ignore: unused_local_variable + final documentBloc = DocumentBloc(view: grid1) + ..add(const DocumentEvent.initial()); + await blocResponseFuture(); + + workspaceSetting = await FolderEventReadCurrentWorkspace() + .send() + .then((result) => result.fold((l) => l, (r) => throw Exception())); + workspaceSetting.latestView.id == grid1.id; + }); +} diff --git a/frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/create_page_test.dart similarity index 62% rename from frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart rename to frontend/app_flowy/test/bloc_test/home_test/create_page_test.dart index e7c0477636..fe0288cadd 100644 --- a/frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart +++ b/frontend/app_flowy/test/bloc_test/home_test/create_page_test.dart @@ -1,5 +1,5 @@ import 'package:app_flowy/plugins/board/board.dart'; -import 'package:app_flowy/plugins/doc/document.dart'; +import 'package:app_flowy/plugins/document/document.dart'; import 'package:app_flowy/plugins/grid/grid.dart'; import 'package:app_flowy/workspace/application/app/app_bloc.dart'; import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; @@ -9,17 +9,17 @@ import 'package:bloc_test/bloc_test.dart'; import '../../util.dart'; void main() { - late AppFlowyUnitTest test; + late AppFlowyUnitTest testContext; setUpAll(() async { - test = await AppFlowyUnitTest.ensureInitialized(); + testContext = await AppFlowyUnitTest.ensureInitialized(); }); group( - 'AppBloc', + '$AppBloc', () { late AppPB app; setUp(() async { - app = await test.createTestApp(); + app = await testContext.createTestApp(); }); blocTest( @@ -66,39 +66,4 @@ void main() { ); }, ); - - group('AppBloc', () { - late ViewPB view; - late AppPB app; - setUpAll(() async { - app = await test.createTestApp(); - }); - - blocTest( - "create a document", - build: () => AppBloc(app: app)..add(const AppEvent.initial()), - act: (bloc) { - bloc.add(AppEvent.createView("Test document", DocumentPluginBuilder())); - }, - wait: blocResponseDuration(), - verify: (bloc) { - assert(bloc.state.views.length == 1); - view = bloc.state.views.last; - }, - ); - blocTest( - "delete the document", - build: () => AppBloc(app: app)..add(const AppEvent.initial()), - act: (bloc) => bloc.add(AppEvent.deleteView(view.id)), - ); - blocTest( - "verify the document is exist", - build: () => AppBloc(app: app)..add(const AppEvent.initial()), - act: (bloc) => bloc.add(const AppEvent.loadViews()), - wait: blocResponseDuration(), - verify: (bloc) { - assert(bloc.state.views.isEmpty); - }, - ); - }); } diff --git a/frontend/app_flowy/test/bloc_test/home_test/home_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/home_bloc_test.dart new file mode 100644 index 0000000000..abd43a03b4 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/home_test/home_bloc_test.dart @@ -0,0 +1,74 @@ +import 'package:app_flowy/plugins/document/application/doc_bloc.dart'; +import 'package:app_flowy/plugins/document/document.dart'; +import 'package:app_flowy/workspace/application/app/app_bloc.dart'; +import 'package:app_flowy/workspace/application/home/home_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flowy_sdk/dispatch/dispatch.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../util.dart'; + +void main() { + late AppFlowyUnitTest testContext; + late WorkspaceSettingPB workspaceSetting; + setUpAll(() async { + testContext = await AppFlowyUnitTest.ensureInitialized(); + }); + + setUp(() async { + workspaceSetting = await FolderEventReadCurrentWorkspace() + .send() + .then((result) => result.fold((l) => l, (r) => throw Exception())); + await blocResponseFuture(); + }); + + group('$HomeBloc', () { + blocTest( + "initial", + build: () => HomeBloc(testContext.userProfile, workspaceSetting) + ..add(const HomeEvent.initial()), + wait: blocResponseDuration(), + verify: (bloc) { + assert(bloc.state.workspaceSetting.hasLatestView()); + }, + ); + }); + + group('$HomeBloc', () { + late AppPB app; + late ViewPB latestCreatedView; + late HomeBloc homeBloc; + setUpAll(() async { + app = await testContext.createTestApp(); + homeBloc = HomeBloc(testContext.userProfile, workspaceSetting) + ..add(const HomeEvent.initial()); + }); + + blocTest( + "create a document view", + build: () => AppBloc(app: app)..add(const AppEvent.initial()), + act: (bloc) async { + bloc.add(AppEvent.createView("New document", DocumentPluginBuilder())); + }, + wait: blocResponseDuration(), + verify: (bloc) { + latestCreatedView = bloc.state.views.last; + }, + ); + + blocTest( + "open the document", + build: () => DocumentBloc(view: latestCreatedView) + ..add(const DocumentEvent.initial()), + wait: blocResponseDuration(), + ); + + test('check the latest view is the document', () async { + assert(homeBloc.state.workspaceSetting.latestView.id == + latestCreatedView.id); + }); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/home_test/menu_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/menu_bloc_test.dart new file mode 100644 index 0000000000..c8f38d9250 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/home_test/menu_bloc_test.dart @@ -0,0 +1,66 @@ +import 'package:app_flowy/workspace/application/menu/menu_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../util.dart'; + +void main() { + late AppFlowyUnitTest test; + setUpAll(() async { + test = await AppFlowyUnitTest.ensureInitialized(); + }); + + group('$MenuBloc', () { + late MenuBloc menuBloc; + setUp(() async { + menuBloc = MenuBloc( + user: test.userProfile, + workspace: test.currentWorkspace, + )..add(const MenuEvent.initial()); + + await blocResponseFuture(); + }); + blocTest( + "assert initial apps is the build-in app", + build: () => menuBloc, + wait: blocResponseDuration(), + verify: (bloc) { + assert(bloc.state.apps.length == 1); + }, + ); + // + blocTest( + "create apps", + build: () => menuBloc, + act: (bloc) async { + bloc.add(const MenuEvent.createApp("App 1")); + await blocResponseFuture(); + bloc.add(const MenuEvent.createApp("App 2")); + await blocResponseFuture(); + bloc.add(const MenuEvent.createApp("App 3")); + }, + wait: blocResponseDuration(), + verify: (bloc) { + // apps[0] is the build-in app + assert(bloc.state.apps[1].name == 'App 1'); + assert(bloc.state.apps[2].name == 'App 2'); + assert(bloc.state.apps[3].name == 'App 3'); + }, + ); + blocTest( + "reorder apps", + build: () => menuBloc, + act: (bloc) async { + bloc.add(const MenuEvent.moveApp(1, 3)); + }, + wait: blocResponseDuration(), + verify: (bloc) { + assert(bloc.state.apps[1].name == 'App 2'); + assert(bloc.state.apps[2].name == 'App 3'); + assert(bloc.state.apps[3].name == 'App 1'); + }, + ); + }); + + // +} diff --git a/frontend/app_flowy/test/bloc_test/home_test/trash_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/trash_bloc_test.dart new file mode 100644 index 0000000000..fd3b87e822 --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/home_test/trash_bloc_test.dart @@ -0,0 +1,100 @@ +import 'package:app_flowy/plugins/document/document.dart'; +import 'package:app_flowy/plugins/trash/application/trash_bloc.dart'; +import 'package:app_flowy/workspace/application/app/app_bloc.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart'; +import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../util.dart'; + +class TrashTestContext { + late AppPB app; + late AppBloc appBloc; + late List allViews; + final AppFlowyUnitTest unitTest; + + TrashTestContext(this.unitTest); + + Future initialize() async { + app = await unitTest.createTestApp(); + appBloc = AppBloc(app: app)..add(const AppEvent.initial()); + + appBloc.add(AppEvent.createView( + "Document 1", + DocumentPluginBuilder(), + )); + await blocResponseFuture(); + + appBloc.add(AppEvent.createView( + "Document 2", + DocumentPluginBuilder(), + )); + await blocResponseFuture(); + + appBloc.add( + AppEvent.createView( + "Document 3", + DocumentPluginBuilder(), + ), + ); + await blocResponseFuture(); + + allViews = [...appBloc.state.app.belongings.items]; + assert(allViews.length == 3); + } +} + +void main() { + late AppFlowyUnitTest unitTest; + setUpAll(() async { + unitTest = await AppFlowyUnitTest.ensureInitialized(); + }); + + // 1. Create three views + // 2. Delete a view and check the state + // 3. Delete all views and check the state + // 4. Put back a view + // 5. Put back all views + + group('trash test: ', () { + test('delete a view', () async { + final context = TrashTestContext(unitTest); + await context.initialize(); + final trashBloc = TrashBloc()..add(const TrashEvent.initial()); + await blocResponseFuture(millisecond: 200); + + // delete a view + final deletedView = context.appBloc.state.app.belongings.items[0]; + context.appBloc.add(AppEvent.deleteView(deletedView.id)); + await blocResponseFuture(); + assert(context.appBloc.state.app.belongings.items.length == 2); + assert(trashBloc.state.objects.length == 1); + assert(trashBloc.state.objects.first.id == deletedView.id); + + // put back + trashBloc.add(TrashEvent.putback(deletedView.id)); + await blocResponseFuture(); + assert(context.appBloc.state.app.belongings.items.length == 3); + assert(trashBloc.state.objects.isEmpty); + + // delete all views + for (final view in context.allViews) { + context.appBloc.add(AppEvent.deleteView(view.id)); + await blocResponseFuture(); + } + assert(trashBloc.state.objects[0].id == context.allViews[0].id); + assert(trashBloc.state.objects[1].id == context.allViews[1].id); + assert(trashBloc.state.objects[2].id == context.allViews[2].id); + + // delete a view permanently + trashBloc.add(TrashEvent.delete(trashBloc.state.objects[0])); + await blocResponseFuture(); + assert(trashBloc.state.objects.length == 2); + + // delete all view permanently + trashBloc.add(const TrashEvent.deleteAll()); + await blocResponseFuture(); + assert(trashBloc.state.objects.isEmpty); + }); + }); +} diff --git a/frontend/app_flowy/test/bloc_test/home_test/view_bloc_test.dart b/frontend/app_flowy/test/bloc_test/home_test/view_bloc_test.dart new file mode 100644 index 0000000000..a702d653cf --- /dev/null +++ b/frontend/app_flowy/test/bloc_test/home_test/view_bloc_test.dart @@ -0,0 +1,67 @@ +import 'package:app_flowy/plugins/document/document.dart'; +import 'package:app_flowy/workspace/application/app/app_bloc.dart'; +import 'package:app_flowy/workspace/application/view/view_bloc.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../util.dart'; + +void main() { + late AppFlowyUnitTest test; + setUpAll(() async { + test = await AppFlowyUnitTest.ensureInitialized(); + }); + + group('$ViewBloc', () { + late AppBloc appBloc; + + setUpAll(() async { + final app = await test.createTestApp(); + appBloc = AppBloc(app: app)..add(const AppEvent.initial()); + appBloc.add(AppEvent.createView( + "Test document", + DocumentPluginBuilder(), + )); + await blocResponseFuture(); + }); + + blocTest( + "rename view", + build: () => ViewBloc(view: appBloc.state.views.first) + ..add(const ViewEvent.initial()), + act: (bloc) { + bloc.add(const ViewEvent.rename('Hello world')); + }, + wait: blocResponseDuration(), + verify: (bloc) { + assert(bloc.state.view.name == "Hello world"); + }, + ); + + blocTest( + "duplicate view", + build: () => ViewBloc(view: appBloc.state.views.first) + ..add(const ViewEvent.initial()), + act: (bloc) { + bloc.add(const ViewEvent.duplicate()); + }, + wait: blocResponseDuration(), + verify: (bloc) { + assert(appBloc.state.views.length == 2); + }, + ); + + blocTest( + "delete view", + build: () => ViewBloc(view: appBloc.state.views.first) + ..add(const ViewEvent.initial()), + act: (bloc) { + bloc.add(const ViewEvent.delete()); + }, + wait: blocResponseDuration(), + verify: (bloc) { + assert(appBloc.state.views.length == 1); + }, + ); + }); +} diff --git a/frontend/app_flowy/test/unit_test/select_option_split_text_input.dart b/frontend/app_flowy/test/unit_test/select_option_split_text_input.dart new file mode 100644 index 0000000000..892b0dba91 --- /dev/null +++ b/frontend/app_flowy/test/unit_test/select_option_split_text_input.dart @@ -0,0 +1,46 @@ +import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const textSeparators = [',']; + + group('split input unit test', () { + test('empty input', () { + List result = splitInput(' ', textSeparators); + expect(result[0], []); + expect(result[1], ''); + + result = splitInput(', , , ', textSeparators); + expect(result[0], []); + expect(result[1], ''); + }); + + test('simple input', () { + List result = splitInput('exampleTag', textSeparators); + expect(result[0], []); + expect(result[1], 'exampleTag'); + + result = splitInput('tag with longer name', textSeparators); + expect(result[0], []); + expect(result[1], 'tag with longer name'); + + result = splitInput('trailing space ', textSeparators); + expect(result[0], []); + expect(result[1], 'trailing space '); + }); + + test('input with commas', () { + List result = splitInput('a, b, c', textSeparators); + expect(result[0], ['a', 'b']); + expect(result[1], 'c'); + + result = splitInput('a, b, c, ', textSeparators); + expect(result[0], ['a', 'b', 'c']); + expect(result[1], ''); + + result = splitInput(',tag 1 ,2nd tag, third tag ', textSeparators); + expect(result[0], ['tag 1', '2nd tag']); + expect(result[1], 'third tag '); + }); + }); +} diff --git a/frontend/app_flowy/test/util.dart b/frontend/app_flowy/test/util.dart index b8367f90fe..3637cf0a22 100644 --- a/frontend/app_flowy/test/util.dart +++ b/frontend/app_flowy/test/util.dart @@ -87,6 +87,15 @@ class AppFlowyUnitTest { (error) => throw Exception(error), ); } + + Future> loadApps() async { + final result = await workspaceService.getApps(); + + return result.fold( + (apps) => apps, + (error) => throw Exception(error), + ); + } } void _pathProviderInitialized() { @@ -104,6 +113,10 @@ class FlowyTestApp implements EntryPoint { } } -Duration blocResponseDuration({int millseconds = 100}) { - return Duration(milliseconds: millseconds); +Future blocResponseFuture({int millisecond = 200}) { + return Future.delayed(Duration(milliseconds: millisecond)); +} + +Duration blocResponseDuration({int milliseconds = 200}) { + return Duration(milliseconds: milliseconds); } diff --git a/frontend/app_flowy/test/widget_test/select_option_text_field_test.dart b/frontend/app_flowy/test/widget_test/select_option_text_field_test.dart new file mode 100644 index 0000000000..01e36edd85 --- /dev/null +++ b/frontend/app_flowy/test/widget_test/select_option_text_field_test.dart @@ -0,0 +1,72 @@ +import 'dart:collection'; + +import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/text_field.dart'; +import 'package:flowy_sdk/protobuf/flowy-grid/protobuf.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:textfield_tags/textfield_tags.dart'; + +import '../bloc_test/grid_test/util.dart'; + +void main() { + setUpAll(() { + AppFlowyGridTest.ensureInitialized(); + }); + + group('text_field.dart', () { + String submit = ''; + String remainder = ''; + List select = []; + + final textField = SelectOptionTextField( + options: const [], + selectedOptionMap: LinkedHashMap(), + distanceToText: 0.0, + tagController: TextfieldTagsController(), + onSubmitted: (text) => submit = text, + onPaste: (options, remaining) { + remainder = remaining; + select = options; + }, + newText: (text) => remainder = text, + textSeparators: const [','], + textController: TextEditingController(), + ); + + testWidgets('SelectOptionTextField callback outputs', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Material( + child: textField, + ), + ), + ); + + // test that the input field exists + expect(find.byType(TextField), findsOneWidget); + + // simulate normal input + await tester.enterText(find.byType(TextField), 'abcd'); + expect(remainder, 'abcd'); + + await tester.enterText(find.byType(TextField), ' '); + expect(remainder, ''); + + // test submit functionality (aka pressing enter) + await tester.enterText(find.byType(TextField), 'an option'); + await tester.testTextInput.receiveAction(TextInputAction.done); + expect(submit, 'an option'); + + submit = ''; + await tester.enterText(find.byType(TextField), ' '); + await tester.testTextInput.receiveAction(TextInputAction.done); + expect(submit, ''); + + // test inputs containing commas + await tester.enterText(find.byType(TextField), 'a a, bbbb , c'); + expect(remainder, 'c'); + expect(select, ['a a', 'bbbb']); + }); + }); +} diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock index e9f528015a..5d49dc217e 100644 --- a/frontend/rust-lib/Cargo.lock +++ b/frontend/rust-lib/Cargo.lock @@ -861,6 +861,7 @@ dependencies = [ "flowy-derive", "flowy-document", "flowy-error", + "flowy-http-model", "flowy-revision", "flowy-sync", "flowy-test", @@ -871,6 +872,7 @@ dependencies = [ "lib-ot", "lib-ws", "log", + "md5", "protobuf", "rand 0.8.5", "serde", @@ -926,17 +928,18 @@ dependencies = [ "flowy-document", "flowy-error", "flowy-folder", - "flowy-folder-data-model", + "flowy-http-model", "flowy-revision", "flowy-sync", "flowy-test", + "folder-rev-model", "futures", "lazy_static", "lib-dispatch", "lib-infra", "lib-ot", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "pin-project", "protobuf", "serde", @@ -948,31 +951,12 @@ dependencies = [ "unicode-segmentation", ] -[[package]] -name = "flowy-folder-data-model" -version = "0.1.0" -dependencies = [ - "bytes", - "chrono", - "derive_more", - "flowy-derive", - "flowy-error-code", - "lib-infra", - "log", - "nanoid", - "protobuf", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros", - "unicode-segmentation", -] - [[package]] name = "flowy-grid" version = "0.1.0" dependencies = [ + "anyhow", + "async-stream", "atomic_refcell", "bytes", "chrono", @@ -985,11 +969,13 @@ dependencies = [ "flowy-derive", "flowy-error", "flowy-grid", - "flowy-grid-data-model", + "flowy-http-model", "flowy-revision", "flowy-sync", + "flowy-task", "flowy-test", "futures", + "grid-rev-model", "indexmap", "lazy_static", "lib-dispatch", @@ -1012,18 +998,14 @@ dependencies = [ ] [[package]] -name = "flowy-grid-data-model" +name = "flowy-http-model" version = "0.1.0" dependencies = [ "bytes", - "flowy-error-code", - "indexmap", + "flowy-derive", "lib-infra", - "nanoid", - "serde", - "serde_json", - "serde_repr", - "tracing", + "md5", + "protobuf", ] [[package]] @@ -1039,9 +1021,10 @@ dependencies = [ "flowy-document", "flowy-error", "flowy-folder", - "flowy-folder-data-model", + "flowy-http-model", "flowy-sync", "flowy-user", + "folder-rev-model", "futures-util", "http-flowy", "hyper", @@ -1051,7 +1034,7 @@ dependencies = [ "lib-ws", "log", "nanoid", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", "reqwest", "serde", @@ -1070,14 +1053,14 @@ dependencies = [ "async-stream", "bytes", "dashmap", - "diesel", - "diesel_derives", - "flowy-database", "flowy-error", - "flowy-sync", + "flowy-http-model", + "flowy-revision", "futures-util", "lib-infra", "lib-ws", + "nanoid", + "parking_lot 0.12.1", "serde", "serde_json", "strum", @@ -1090,29 +1073,23 @@ dependencies = [ name = "flowy-sdk" version = "0.1.0" dependencies = [ - "bincode", "bytes", - "claim 0.5.0", - "color-eyre", "flowy-database", "flowy-document", "flowy-folder", "flowy-grid", - "flowy-grid-data-model", + "flowy-http-model", "flowy-net", "flowy-revision", - "flowy-sync", + "flowy-task", "flowy-user", "futures-core", - "futures-util", + "grid-rev-model", "lib-dispatch", "lib-infra", "lib-log", "lib-ws", - "log", - "parking_lot 0.11.2", - "protobuf", - "serde", + "parking_lot 0.12.1", "tokio", "tracing", ] @@ -1127,14 +1104,14 @@ dependencies = [ "dashmap", "dissimilar", "flowy-derive", - "flowy-folder-data-model", - "flowy-grid-data-model", + "flowy-http-model", + "folder-rev-model", "futures", + "grid-rev-model", "lib-infra", "lib-ot", "log", - "md5", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", "serde", "serde_json", @@ -1145,6 +1122,19 @@ dependencies = [ "url", ] +[[package]] +name = "flowy-task" +version = "0.1.0" +dependencies = [ + "anyhow", + "atomic_refcell", + "futures", + "lib-infra", + "rand 0.8.5", + "tokio", + "tracing", +] + [[package]] name = "flowy-test" version = "0.1.0" @@ -1154,6 +1144,7 @@ dependencies = [ "claim 0.4.0", "claim 0.5.0", "fake", + "flowy-document", "flowy-folder", "flowy-net", "flowy-sdk", @@ -1198,7 +1189,7 @@ dependencies = [ "log", "nanoid", "once_cell", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", "quickcheck", "quickcheck_macros", @@ -1221,6 +1212,20 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "folder-rev-model" +version = "0.1.0" +dependencies = [ + "bytes", + "chrono", + "nanoid", + "serde", + "serde_json", + "serde_repr", + "strum", + "strum_macros", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1416,6 +1421,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "grid-rev-model" +version = "0.1.0" +dependencies = [ + "bytes", + "flowy-error-code", + "indexmap", + "nanoid", + "serde", + "serde_json", + "serde_repr", +] + [[package]] name = "h2" version = "0.3.10" @@ -1772,6 +1790,7 @@ dependencies = [ "bytes", "dashmap", "derive_more", + "indexmap", "indextree", "lazy_static", "log", @@ -1815,7 +1834,7 @@ dependencies = [ "futures-util", "lib-infra", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "paste", "pin-project", "protobuf", diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml index 075258b97b..cf12cbe38f 100644 --- a/frontend/rust-lib/Cargo.toml +++ b/frontend/rust-lib/Cargo.toml @@ -15,6 +15,7 @@ members = [ "flowy-error", "flowy-revision", "flowy-grid", + "flowy-task", ] [profile.dev] diff --git a/frontend/rust-lib/dart-ffi/Cargo.toml b/frontend/rust-lib/dart-ffi/Cargo.toml index 1cd0d04039..71df69e4cb 100644 --- a/frontend/rust-lib/dart-ffi/Cargo.toml +++ b/frontend/rust-lib/dart-ffi/Cargo.toml @@ -38,6 +38,5 @@ openssl_vendored = ["flowy-sdk/openssl_vendored"] [build-dependencies] lib-infra = { path = "../../../shared-lib/lib-infra", features = [ - "protobuf_file_gen", "dart", ] } diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs index 24a87b1519..53ac68dfc5 100644 --- a/frontend/rust-lib/dart-ffi/src/lib.rs +++ b/frontend/rust-lib/dart-ffi/src/lib.rs @@ -23,7 +23,7 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 { let path: &str = c_str.to_str().unwrap(); let server_config = get_client_server_configuration().unwrap(); - let config = FlowySDKConfig::new(path, server_config, "appflowy").log_filter("debug"); + let config = FlowySDKConfig::new(path, "appflowy", server_config).log_filter("info"); FLOWY_SDK.get_or_init(|| FlowySDK::new(config)); 0 @@ -44,7 +44,7 @@ pub extern "C" fn async_event(port: i64, input: *const u8, len: usize) { log::error!("sdk not init yet."); return; } - Some(e) => e.dispatcher.clone(), + Some(e) => e.event_dispatcher.clone(), }; let _ = EventDispatcher::async_send_with_callback(dispatcher, request, move |resp: EventResponse| { log::trace!("[FFI]: Post data to dart through {} port", port); @@ -62,7 +62,7 @@ pub extern "C" fn sync_event(input: *const u8, len: usize) -> *const u8 { log::error!("sdk not init yet."); return forget_rust(Vec::default()); } - Some(e) => e.dispatcher.clone(), + Some(e) => e.event_dispatcher.clone(), }; let _response = EventDispatcher::sync_send(dispatcher, request); diff --git a/frontend/rust-lib/dart-notify/Cargo.toml b/frontend/rust-lib/dart-notify/Cargo.toml index 4f55804e53..64469a3f24 100644 --- a/frontend/rust-lib/dart-notify/Cargo.toml +++ b/frontend/rust-lib/dart-notify/Cargo.toml @@ -19,4 +19,4 @@ lib-dispatch = {path = "../lib-dispatch" } dart = ["lib-infra/dart"] [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen"] } \ No newline at end of file +lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] } \ No newline at end of file diff --git a/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/down.sql b/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/down.sql new file mode 100644 index 0000000000..46d0fce4d8 --- /dev/null +++ b/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE grid_view_rev_table; diff --git a/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/up.sql b/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/up.sql new file mode 100644 index 0000000000..34d4e71d41 --- /dev/null +++ b/frontend/rust-lib/flowy-database/migrations/2022-10-22-033122_document/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here +CREATE TABLE document_rev_table ( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + document_id TEXT NOT NULL DEFAULT '', + base_rev_id BIGINT NOT NULL DEFAULT 0, + rev_id BIGINT NOT NULL DEFAULT 0, + data BLOB NOT NULL DEFAULT (x''), + state INTEGER NOT NULL DEFAULT 0 +); diff --git a/frontend/rust-lib/flowy-database/src/macros.rs b/frontend/rust-lib/flowy-database/src/macros.rs index 870938c1d0..e1534bf25f 100644 --- a/frontend/rust-lib/flowy-database/src/macros.rs +++ b/frontend/rust-lib/flowy-database/src/macros.rs @@ -177,20 +177,20 @@ macro_rules! impl_rev_state_map { } } - impl std::convert::From<$target> for crate::disk::RevisionState { + impl std::convert::From<$target> for RevisionState { fn from(s: $target) -> Self { match s { - $target::Sync => crate::disk::RevisionState::Sync, - $target::Ack => crate::disk::RevisionState::Ack, + $target::Sync => RevisionState::Sync, + $target::Ack => RevisionState::Ack, } } } - impl std::convert::From for $target { - fn from(s: crate::disk::RevisionState) -> Self { + impl std::convert::From for $target { + fn from(s: RevisionState) -> Self { match s { - crate::disk::RevisionState::Sync => $target::Sync, - crate::disk::RevisionState::Ack => $target::Ack, + RevisionState::Sync => $target::Sync, + RevisionState::Ack => $target::Ack, } } } diff --git a/frontend/rust-lib/flowy-database/src/schema.rs b/frontend/rust-lib/flowy-database/src/schema.rs index 065a13b85f..19d27e43bc 100644 --- a/frontend/rust-lib/flowy-database/src/schema.rs +++ b/frontend/rust-lib/flowy-database/src/schema.rs @@ -13,6 +13,17 @@ table! { } } +table! { + document_rev_table (id) { + id -> Integer, + document_id -> Text, + base_rev_id -> BigInt, + rev_id -> BigInt, + data -> Binary, + state -> Integer, + } +} + table! { grid_block_index_table (row_id) { row_id -> Text, @@ -133,6 +144,7 @@ table! { allow_tables_to_appear_in_same_query!( app_table, + document_rev_table, grid_block_index_table, grid_meta_rev_table, grid_rev_table, diff --git a/frontend/rust-lib/flowy-document/Cargo.toml b/frontend/rust-lib/flowy-document/Cargo.toml index 6e3ab066a3..4fc1b899ce 100644 --- a/frontend/rust-lib/flowy-document/Cargo.toml +++ b/frontend/rust-lib/flowy-document/Cargo.toml @@ -8,6 +8,7 @@ edition = "2018" [dependencies] flowy-sync = { path = "../../../shared-lib/flowy-sync"} +flowy-http-model = { path = "../../../shared-lib/flowy-http-model"} flowy-derive = { path = "../../../shared-lib/flowy-derive" } lib-ot = { path = "../../../shared-lib/lib-ot" } lib-ws = { path = "../../../shared-lib/lib-ws" } @@ -28,6 +29,7 @@ tokio = {version = "1", features = ["sync"]} tracing = { version = "0.1", features = ["log"] } bytes = { version = "1.1" } +md5 = "0.7.0" strum = "0.21" strum_macros = "0.21" dashmap = "5" @@ -50,7 +52,7 @@ criterion = "0.3" rand = "0.8.5" [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] } +lib-infra = { path = "../../../shared-lib/lib-infra", features = [ "proto_gen"] } [features] sync = [] diff --git a/frontend/rust-lib/flowy-document/src/editor/READ_ME.json b/frontend/rust-lib/flowy-document/src/editor/READ_ME.json new file mode 100644 index 0000000000..b47f43ee16 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/READ_ME.json @@ -0,0 +1,419 @@ +{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "attributes": { + "subtype": "heading", + "heading": "h1" + }, + "delta": [ + { + "insert": "🌟 Welcome to AppFlowy!" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "heading", + "heading": "h2" + }, + "delta": [ + { + "insert": "Here are the basics" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": null + }, + "delta": [ + { + "insert": "Click anywhere and just start typing." + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": null + }, + "delta": [ + { + "insert": "Highlight", + "attributes": { + "backgroundColor": "0x6000BCF0" + } + }, + { + "insert": " any text, and use the editing menu to " + }, + { + "insert": "style", + "attributes": { + "italic": true + } + }, + { + "insert": " " + }, + { + "insert": "your", + "attributes": { + "bold": true + } + }, + { + "insert": " " + }, + { + "insert": "writing", + "attributes": { + "underline": true + } + }, + { + "insert": " " + }, + { + "insert": "however", + "attributes": { + "code": true + } + }, + { + "insert": " you " + }, + { + "insert": "like.", + "attributes": { + "strikethrough": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": null + }, + "delta": [ + { + "insert": "As soon as you type " + }, + { + "insert": "/", + "attributes": { + "code": true + } + }, + { + "insert": " a menu will pop up. Select different types of content blocks you can add." + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": null + }, + "delta": [ + { + "insert": "Type " + }, + { + "insert": "/", + "attributes": { + "code": true + } + }, + { + "insert": " followed by " + }, + { + "insert": "/bullet", + "attributes": { + "code": true + } + }, + { + "insert": " or " + }, + { + "insert": "/c.", + "attributes": { + "code": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": true + }, + "delta": [ + { + "insert": "Click " + }, + { + "insert": "+ New Page ", + "attributes": { + "code": true + } + }, + { + "insert": "button at the bottom of your sidebar to add a new page." + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "checkbox", + "checkbox": null + }, + "delta": [ + { + "insert": "Click " + }, + { + "insert": "+", + "attributes": { + "code": true + } + }, + { + "insert": " next to any page title in the sidebar to quickly add a new subpage." + } + ] + }, + { + "type": "text", + "attributes": { + "checkbox": null + }, + "delta": [] + }, + { + "type": "text", + "attributes": { + "subtype": "heading", + "checkbox": null, + "heading": "h2" + }, + "delta": [ + { + "insert": "Markdown" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "number-list", + "number": 1, + "heading": null + }, + "delta": [ + { + "insert": "Heading " + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "number-list", + "number": 2 + }, + "delta": [ + { + "insert": "bold text", + "attributes": { + "bold": true, + "defaultFormating": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "number-list", + "number": 3 + }, + "delta": [ + { + "insert": "italicized text", + "attributes": { + "italic": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "number-list", + "number": 4, + "number-list": null + }, + "delta": [ + { + "insert": "Ordered List" + } + ] + }, + { + "type": "text", + "attributes": { + "number": 5, + "subtype": "number-list" + }, + "delta": [ + { + "insert": "code", + "attributes": { + "code": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "number": 6, + "subtype": "number-list" + }, + "delta": [ + { + "insert": "Strikethrough", + "attributes": { + "strikethrough": true + } + }, + { + "retain": 1, + "attributes": { + "strikethrough": true + } + } + ] + }, + { + "type": "text", + "attributes": { + "checkbox": null + }, + "delta": [] + }, + { + "type": "text", + "attributes": { + "subtype": "heading", + "checkbox": null, + "heading": "h2" + }, + "delta": [ + { + "insert": "Have a question?" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "quote" + }, + "delta": [ + { + "insert": "Click " + }, + { + "insert": "?", + "attributes": { + "code": true + } + }, + { + "insert": " at the bottom right for help and support." + } + ] + }, + { + "type": "text", + "delta": [] + }, + { + "type": "text", + "attributes": { + "subtype": "heading", + "heading": "h2" + }, + "delta": [ + { + "insert": "Like AppFlowy? Follow us:" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "bulleted-list", + "quote": null + }, + "delta": [ + { + "insert": "GitHub", + "attributes": { + "href": "https://github.com/AppFlowy-IO/AppFlowy" + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "bulleted-list" + }, + "delta": [ + { + "insert": "Twitter: @appflowy" + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": "bulleted-list" + }, + "delta": [ + { + "insert": "Newsletter", + "attributes": { + "href": "https://blog-appflowy.ghost.io/" + } + } + ] + }, + { + "type": "text", + "attributes": { + "subtype": null, + "heading": null + }, + "delta": [] + } + ] + } +} diff --git a/frontend/rust-lib/flowy-document/src/editor/document.rs b/frontend/rust-lib/flowy-document/src/editor/document.rs new file mode 100644 index 0000000000..108913f747 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/document.rs @@ -0,0 +1,112 @@ +use bytes::Bytes; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_http_model::revision::Revision; +use flowy_revision::{RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer}; +use lib_ot::core::{Extension, NodeDataBuilder, NodeOperation, NodeTree, NodeTreeContext, Selection, Transaction}; +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[derive(Debug)] +pub struct Document { + tree: NodeTree, +} + +impl Document { + pub fn new(tree: NodeTree) -> Self { + Self { tree } + } + + pub fn from_transaction(transaction: Transaction) -> FlowyResult { + let tree = NodeTree::from_operations(transaction.operations, make_tree_context())?; + Ok(Self { tree }) + } + + pub fn get_content(&self, pretty: bool) -> FlowyResult { + if pretty { + serde_json::to_string_pretty(self).map_err(|err| FlowyError::serde().context(err)) + } else { + serde_json::to_string(self).map_err(|err| FlowyError::serde().context(err)) + } + } + + pub fn document_md5(&self) -> String { + let bytes = self.tree.to_bytes(); + format!("{:x}", md5::compute(&bytes)) + } + + pub fn get_tree(&self) -> &NodeTree { + &self.tree + } +} + +pub(crate) fn make_tree_context() -> NodeTreeContext { + NodeTreeContext {} +} + +pub fn initial_document_content() -> String { + let delta = DeltaTextOperationBuilder::new().insert("").build(); + let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); + let editor_node = NodeDataBuilder::new("editor").add_node_data(node_data).build(); + let node_operation = NodeOperation::Insert { + path: vec![0].into(), + nodes: vec![editor_node], + }; + let extension = Extension::TextSelection { + before_selection: Selection::default(), + after_selection: Selection::default(), + }; + let transaction = Transaction { + operations: vec![node_operation].into(), + extension, + }; + transaction.to_json().unwrap() +} + +impl std::ops::Deref for Document { + type Target = NodeTree; + + fn deref(&self) -> &Self::Target { + &self.tree + } +} + +impl std::ops::DerefMut for Document { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.tree + } +} + +pub struct DocumentRevisionSerde(); +impl RevisionObjectDeserializer for DocumentRevisionSerde { + type Output = Document; + + fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { + let mut tree = NodeTree::new(make_tree_context()); + let transaction = make_transaction_from_revisions(&revisions)?; + let _ = tree.apply_transaction(transaction)?; + let document = Document::new(tree); + Result::::Ok(document) + } +} + +impl RevisionObjectSerializer for DocumentRevisionSerde { + fn combine_revisions(revisions: Vec) -> FlowyResult { + let transaction = make_transaction_from_revisions(&revisions)?; + Ok(Bytes::from(transaction.to_bytes()?)) + } +} + +pub(crate) struct DocumentRevisionCompress(); +impl RevisionMergeable for DocumentRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + DocumentRevisionSerde::combine_revisions(revisions) + } +} + +#[tracing::instrument(level = "trace", skip_all, err)] +pub fn make_transaction_from_revisions(revisions: &[Revision]) -> FlowyResult { + let mut transaction = Transaction::new(); + for revision in revisions { + let _ = transaction.compose(Transaction::from_bytes(&revision.bytes)?)?; + } + Ok(transaction) +} diff --git a/frontend/rust-lib/flowy-document/src/editor/document_serde.rs b/frontend/rust-lib/flowy-document/src/editor/document_serde.rs new file mode 100644 index 0000000000..460c6dd29f --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/document_serde.rs @@ -0,0 +1,488 @@ +use crate::editor::document::Document; +use bytes::Bytes; +use flowy_error::FlowyResult; +use lib_ot::core::{ + AttributeHashMap, Body, Changeset, Extension, NodeData, NodeId, NodeOperation, NodeTree, NodeTreeContext, Path, + Selection, Transaction, +}; +use lib_ot::text_delta::DeltaTextOperations; +use serde::de::{self, MapAccess, Unexpected, Visitor}; +use serde::ser::{SerializeMap, SerializeSeq}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +impl Serialize for Document { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(1))?; + let _ = map.serialize_key("document")?; + let _ = map.serialize_value(&DocumentContentSerializer(self))?; + map.end() + } +} + +const FIELDS: &[&str] = &["Document"]; + +impl<'de> Deserialize<'de> for Document { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct DocumentVisitor(); + + impl<'de> Visitor<'de> for DocumentVisitor { + type Value = Document; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expect document tree") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut document_node = None; + while let Some(key) = map.next_key()? { + match key { + "document" => { + if document_node.is_some() { + return Err(de::Error::duplicate_field("document")); + } + document_node = Some(map.next_value::()?) + } + s => { + return Err(de::Error::unknown_field(s, FIELDS)); + } + } + } + + match document_node { + Some(document_node) => { + match NodeTree::from_node_data(document_node.into(), NodeTreeContext::default()) { + Ok(tree) => Ok(Document::new(tree)), + Err(err) => Err(de::Error::invalid_value(Unexpected::Other(&format!("{}", err)), &"")), + } + } + None => Err(de::Error::missing_field("document")), + } + } + } + deserializer.deserialize_any(DocumentVisitor()) + } +} + +pub fn make_transaction_from_document_content(content: &str) -> FlowyResult { + let document_node: DocumentNode = serde_json::from_str::(content)?.document; + let document_operation = DocumentOperation::Insert { + path: 0_usize.into(), + nodes: vec![document_node], + }; + let mut document_transaction = DocumentTransaction::default(); + document_transaction.operations.push(document_operation); + Ok(document_transaction.into()) +} + +pub struct DocumentContentSerde {} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct DocumentTransaction { + #[serde(default)] + operations: Vec, + + #[serde(default)] + before_selection: Selection, + + #[serde(default)] + after_selection: Selection, +} + +impl DocumentTransaction { + pub fn to_json(&self) -> FlowyResult { + let json = serde_json::to_string(self)?; + Ok(json) + } + + pub fn to_bytes(&self) -> FlowyResult { + let data = serde_json::to_vec(&self)?; + Ok(Bytes::from(data)) + } + + pub fn from_bytes(bytes: Bytes) -> FlowyResult { + let transaction = serde_json::from_slice(&bytes)?; + Ok(transaction) + } +} + +impl std::convert::From for DocumentTransaction { + fn from(transaction: Transaction) -> Self { + let (operations, extension) = transaction.split(); + let (before_selection, after_selection) = match extension { + Extension::Empty => (Selection::default(), Selection::default()), + Extension::TextSelection { + before_selection, + after_selection, + } => (before_selection, after_selection), + }; + + DocumentTransaction { + operations: operations + .into_iter() + .map(|operation| operation.as_ref().into()) + .collect(), + before_selection, + after_selection, + } + } +} + +impl std::convert::From for Transaction { + fn from(document_transaction: DocumentTransaction) -> Self { + let mut transaction = Transaction::new(); + for document_operation in document_transaction.operations { + transaction.push_operation(document_operation); + } + transaction.extension = Extension::TextSelection { + before_selection: document_transaction.before_selection, + after_selection: document_transaction.after_selection, + }; + transaction + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "op")] +pub enum DocumentOperation { + #[serde(rename = "insert")] + Insert { path: Path, nodes: Vec }, + + #[serde(rename = "delete")] + Delete { path: Path, nodes: Vec }, + + #[serde(rename = "update")] + Update { + path: Path, + attributes: AttributeHashMap, + #[serde(rename = "oldAttributes")] + old_attributes: AttributeHashMap, + }, + + #[serde(rename = "update_text")] + UpdateText { + path: Path, + delta: DeltaTextOperations, + inverted: DeltaTextOperations, + }, +} + +impl std::convert::From for NodeOperation { + fn from(document_operation: DocumentOperation) -> Self { + match document_operation { + DocumentOperation::Insert { path, nodes } => NodeOperation::Insert { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + DocumentOperation::Delete { path, nodes } => NodeOperation::Delete { + path, + + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + DocumentOperation::Update { + path, + attributes, + old_attributes, + } => NodeOperation::Update { + path, + changeset: Changeset::Attributes { + new: attributes, + old: old_attributes, + }, + }, + DocumentOperation::UpdateText { path, delta, inverted } => NodeOperation::Update { + path, + changeset: Changeset::Delta { delta, inverted }, + }, + } + } +} + +impl std::convert::From<&NodeOperation> for DocumentOperation { + fn from(node_operation: &NodeOperation) -> Self { + let node_operation = node_operation.clone(); + match node_operation { + NodeOperation::Insert { path, nodes } => DocumentOperation::Insert { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + NodeOperation::Update { path, changeset } => match changeset { + Changeset::Delta { delta, inverted } => DocumentOperation::UpdateText { path, delta, inverted }, + Changeset::Attributes { new, old } => DocumentOperation::Update { + path, + attributes: new, + old_attributes: old, + }, + }, + NodeOperation::Delete { path, nodes } => DocumentOperation::Delete { + path, + nodes: nodes.into_iter().map(|node| node.into()).collect(), + }, + } + } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct DocumentNode { + #[serde(rename = "type")] + pub node_type: String, + + #[serde(skip_serializing_if = "AttributeHashMap::is_empty")] + #[serde(default)] + pub attributes: AttributeHashMap, + + #[serde(skip_serializing_if = "DeltaTextOperations::is_empty")] + #[serde(default)] + pub delta: DeltaTextOperations, + + #[serde(skip_serializing_if = "Vec::is_empty")] + #[serde(default)] + pub children: Vec, +} + +impl DocumentNode { + pub fn new() -> Self { + Self::default() + } +} + +impl std::convert::From for DocumentNode { + fn from(node_data: NodeData) -> Self { + let delta = if let Body::Delta(operations) = node_data.body { + operations + } else { + DeltaTextOperations::default() + }; + DocumentNode { + node_type: node_data.node_type, + attributes: node_data.attributes, + delta, + children: node_data.children.into_iter().map(DocumentNode::from).collect(), + } + } +} + +impl std::convert::From for NodeData { + fn from(document_node: DocumentNode) -> Self { + NodeData { + node_type: document_node.node_type, + attributes: document_node.attributes, + body: Body::Delta(document_node.delta), + children: document_node.children.into_iter().map(|child| child.into()).collect(), + } + } +} + +#[derive(Debug, Deserialize)] +struct DocumentContentDeserializer { + document: DocumentNode, +} + +#[derive(Debug)] +struct DocumentContentSerializer<'a>(pub &'a Document); + +impl<'a> Serialize for DocumentContentSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let tree = self.0.get_tree(); + let root_node_id = tree.root_node_id(); + + // transform the NodeData to DocumentNodeData + let get_document_node_data = |node_id: NodeId| tree.get_node_data(node_id).map(DocumentNode::from); + + let mut children = tree.get_children_ids(root_node_id); + if children.len() == 1 { + let node_id = children.pop().unwrap(); + match get_document_node_data(node_id) { + None => serializer.serialize_str(""), + Some(node_data) => node_data.serialize(serializer), + } + } else { + let mut seq = serializer.serialize_seq(Some(children.len()))?; + for child in children { + if let Some(node_data) = get_document_node_data(child) { + let _ = seq.serialize_element(&node_data)?; + } + } + seq.end() + } + } +} + +#[cfg(test)] +mod tests { + use crate::editor::document::Document; + use crate::editor::document_serde::DocumentTransaction; + use crate::editor::initial_read_me; + + #[test] + fn load_read_me() { + let _ = initial_read_me(); + } + + #[test] + fn transaction_deserialize_update_text_operation_test() { + // bold + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":3,"attributes":{"bold":true}}],"inverted":[{"retain":3,"attributes":{"bold":null}}]}],"after_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":3}}}"#; + let _ = serde_json::from_str::(json).unwrap(); + + // delete character + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"retain":2},{"delete":1}],"inverted":[{"retain":2},{"insert":"C","attributes":{"bold":true}}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[0],"offset":3},"end":{"path":[0],"offset":3}}}"#; + let _ = serde_json::from_str::(json).unwrap(); + } + + #[test] + fn transaction_deserialize_insert_operation_test() { + let json = r#"{"operations":[{"op":"update_text","path":[0],"delta":[{"insert":"a"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0],"offset":1},"end":{"path":[0],"offset":1}},"before_selection":{"start":{"path":[0],"offset":0},"end":{"path":[0],"offset":0}}}"#; + let _ = serde_json::from_str::(json).unwrap(); + } + + #[test] + fn transaction_deserialize_delete_operation_test() { + let json = r#"{"operations": [{"op":"delete","path":[1],"nodes":[{"type":"text","delta":[]}]}],"after_selection":{"start":{"path":[0],"offset":2},"end":{"path":[0],"offset":2}},"before_selection":{"start":{"path":[1],"offset":0},"end":{"path":[1],"offset":0}}}"#; + let _transaction = serde_json::from_str::(json).unwrap(); + } + + #[test] + fn transaction_deserialize_update_attribute_operation_test() { + // let json = r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3,"attributes":{"bold":true}},"oldAttributes":{"retain":3,"attributes":{"bold":null}}}]}"#; + // let transaction = serde_json::from_str::(&json).unwrap(); + + let json = + r#"{"operations":[{"op":"update","path":[0],"attributes":{"retain":3},"oldAttributes":{"retain":3}}]}"#; + let _ = serde_json::from_str::(json).unwrap(); + } + + #[test] + fn document_serde_test() { + let document: Document = serde_json::from_str(EXAMPLE_DOCUMENT).unwrap(); + let _ = serde_json::to_string_pretty(&document).unwrap(); + } + + // #[test] + // fn document_operation_compose_test() { + // let json = include_str!("./test.json"); + // let transaction: Transaction = Transaction::from_json(json).unwrap(); + // let json = transaction.to_json().unwrap(); + // // let transaction: Transaction = Transaction::from_json(&json).unwrap(); + // let document = Document::from_transaction(transaction).unwrap(); + // let content = document.get_content(false).unwrap(); + // println!("{}", json); + // } + + const EXAMPLE_DOCUMENT: &str = r#"{ + "document": { + "type": "editor", + "children": [ + { + "type": "image", + "attributes": { + "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg", + "align": "center" + } + }, + { + "type": "text", + "attributes": { "subtype": "heading", "heading": "h1" }, + "delta": [ + { "insert": "👋 " }, + { "insert": "Welcome to ", "attributes": { "bold": true } }, + { + "insert": "AppFlowy Editor", + "attributes": { + "href": "appflowy.io", + "italic": true, + "bold": true + } + } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { "insert": "AppFlowy Editor is a " }, + { "insert": "highly customizable", "attributes": { "bold": true } }, + { "insert": " " }, + { "insert": "rich-text editor", "attributes": { "italic": true } }, + { "insert": " for " }, + { "insert": "Flutter", "attributes": { "underline": true } } + ] + }, + { + "type": "text", + "attributes": { "checkbox": true, "subtype": "checkbox" }, + "delta": [{ "insert": "Customizable" }] + }, + { + "type": "text", + "attributes": { "checkbox": true, "subtype": "checkbox" }, + "delta": [{ "insert": "Test-covered" }] + }, + { + "type": "text", + "attributes": { "checkbox": false, "subtype": "checkbox" }, + "delta": [{ "insert": "more to come!" }] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "attributes": { "subtype": "quote" }, + "delta": [{ "insert": "Here is an example you can give a try" }] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { "insert": "You can also use " }, + { + "insert": "AppFlowy Editor", + "attributes": { + "italic": true, + "bold": true, + "backgroundColor": "0x6000BCF0" + } + }, + { "insert": " as a component to build your own app." } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "attributes": { "subtype": "bulleted-list" }, + "delta": [{ "insert": "Use / to insert blocks" }] + }, + { + "type": "text", + "attributes": { "subtype": "bulleted-list" }, + "delta": [ + { + "insert": "Select text to trigger to the toolbar to format your notes." + } + ] + }, + { "type": "text", "delta": [] }, + { + "type": "text", + "delta": [ + { + "insert": "If you have questions or feedback, please submit an issue on Github or join the community along with 1000+ builders!" + } + ] + } + ] + } +} +"#; +} diff --git a/frontend/rust-lib/flowy-document/src/editor/editor.rs b/frontend/rust-lib/flowy-document/src/editor/editor.rs new file mode 100644 index 0000000000..6480e86ff1 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/editor.rs @@ -0,0 +1,122 @@ +use crate::editor::document::{Document, DocumentRevisionSerde}; +use crate::editor::document_serde::DocumentTransaction; +use crate::editor::make_transaction_from_revisions; +use crate::editor::queue::{Command, CommandSender, DocumentQueue}; +use crate::{DocumentEditor, DocumentUser}; +use bytes::Bytes; +use flowy_database::ConnectionPool; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_http_model::ws_data::ServerRevisionWSData; +use flowy_revision::{RevisionCloudService, RevisionManager}; +use lib_infra::future::FutureResult; +use lib_ot::core::Transaction; +use lib_ws::WSConnectState; +use std::any::Any; +use std::sync::Arc; +use tokio::sync::{mpsc, oneshot}; + +pub struct AppFlowyDocumentEditor { + #[allow(dead_code)] + doc_id: String, + command_sender: CommandSender, + rev_manager: Arc>>, +} + +impl AppFlowyDocumentEditor { + pub async fn new( + doc_id: &str, + user: Arc, + mut rev_manager: RevisionManager>, + cloud_service: Arc, + ) -> FlowyResult> { + let document = rev_manager + .initialize::(Some(cloud_service)) + .await?; + let rev_manager = Arc::new(rev_manager); + let command_sender = spawn_edit_queue(user, rev_manager.clone(), document); + let doc_id = doc_id.to_string(); + let editor = Arc::new(Self { + doc_id, + command_sender, + rev_manager, + }); + Ok(editor) + } + + pub async fn apply_transaction(&self, transaction: Transaction) -> FlowyResult<()> { + let (ret, rx) = oneshot::channel::>(); + let _ = self + .command_sender + .send(Command::ComposeTransaction { transaction, ret }) + .await; + let _ = rx.await.map_err(internal_error)??; + Ok(()) + } + + pub async fn get_content(&self, pretty: bool) -> FlowyResult { + let (ret, rx) = oneshot::channel::>(); + let _ = self + .command_sender + .send(Command::GetDocumentContent { pretty, ret }) + .await; + let content = rx.await.map_err(internal_error)??; + Ok(content) + } + + pub async fn duplicate_document(&self) -> FlowyResult { + let revisions = self.rev_manager.load_revisions().await?; + let transaction = make_transaction_from_revisions(&revisions)?; + let json = transaction.to_json()?; + Ok(json) + } +} + +fn spawn_edit_queue( + user: Arc, + rev_manager: Arc>>, + document: Document, +) -> CommandSender { + let (sender, receiver) = mpsc::channel(1000); + let queue = DocumentQueue::new(user, rev_manager, document, receiver); + tokio::spawn(queue.run()); + sender +} + +impl DocumentEditor for Arc { + #[tracing::instrument(name = "close document editor", level = "trace", skip_all)] + fn close(&self) { + let rev_manager = self.rev_manager.clone(); + tokio::spawn(async move { + rev_manager.close().await; + }); + } + + fn export(&self) -> FutureResult { + let this = self.clone(); + FutureResult::new(async move { this.get_content(false).await }) + } + + fn duplicate(&self) -> FutureResult { + let this = self.clone(); + FutureResult::new(async move { this.duplicate_document().await }) + } + + fn receive_ws_data(&self, _data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { + FutureResult::new(async move { Ok(()) }) + } + + fn receive_ws_state(&self, _state: &WSConnectState) {} + + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { + let this = self.clone(); + FutureResult::new(async move { + let transaction = DocumentTransaction::from_bytes(data)?; + let _ = this.apply_transaction(transaction.into()).await?; + Ok(()) + }) + } + + fn as_any(&self) -> &dyn Any { + self + } +} diff --git a/frontend/rust-lib/flowy-document/src/editor/mod.rs b/frontend/rust-lib/flowy-document/src/editor/mod.rs new file mode 100644 index 0000000000..6d1abf4a7a --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/mod.rs @@ -0,0 +1,16 @@ +#![allow(clippy::module_inception)] +mod document; +mod document_serde; +mod editor; +mod queue; + +pub use document::*; +pub use document_serde::*; +pub use editor::*; + +#[inline] +pub fn initial_read_me() -> String { + let document_content = include_str!("READ_ME.json"); + let transaction = make_transaction_from_document_content(document_content).unwrap(); + transaction.to_json().unwrap() +} diff --git a/frontend/rust-lib/flowy-document/src/editor/queue.rs b/frontend/rust-lib/flowy-document/src/editor/queue.rs new file mode 100644 index 0000000000..62b4f0e6ac --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/editor/queue.rs @@ -0,0 +1,95 @@ +use crate::editor::document::Document; +use crate::DocumentUser; +use async_stream::stream; +use bytes::Bytes; +use flowy_error::FlowyError; +use flowy_http_model::revision::{RevId, Revision}; +use flowy_revision::RevisionManager; +use futures::stream::StreamExt; +use lib_ot::core::Transaction; + +use flowy_database::ConnectionPool; +use std::sync::Arc; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio::sync::{oneshot, RwLock}; + +pub struct DocumentQueue { + #[allow(dead_code)] + user: Arc, + document: Arc>, + #[allow(dead_code)] + rev_manager: Arc>>, + receiver: Option, +} + +impl DocumentQueue { + pub fn new( + user: Arc, + rev_manager: Arc>>, + document: Document, + receiver: CommandReceiver, + ) -> Self { + let document = Arc::new(RwLock::new(document)); + Self { + user, + document, + rev_manager, + receiver: Some(receiver), + } + } + + pub async fn run(mut self) { + let mut receiver = self.receiver.take().expect("Only take once"); + let stream = stream! { + loop { + match receiver.recv().await { + Some(msg) => yield msg, + None => break, + } + } + }; + stream + .for_each(|command| async { + match self.handle_command(command).await { + Ok(_) => {} + Err(e) => tracing::debug!("[DocumentQueue]: {}", e), + } + }) + .await; + } + + async fn handle_command(&self, command: Command) -> Result<(), FlowyError> { + match command { + Command::ComposeTransaction { transaction, ret } => { + self.document.write().await.apply_transaction(transaction.clone())?; + let _ = self + .save_local_operations(transaction, self.document.read().await.document_md5()) + .await?; + let _ = ret.send(Ok(())); + } + Command::GetDocumentContent { pretty, ret } => { + let content = self.document.read().await.get_content(pretty)?; + let _ = ret.send(Ok(content)); + } + } + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self, transaction, md5), err)] + async fn save_local_operations(&self, transaction: Transaction, md5: String) -> Result { + let bytes = Bytes::from(transaction.to_bytes()?); + let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); + let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, bytes, md5); + let _ = self.rev_manager.add_local_revision(&revision).await?; + Ok(rev_id.into()) + } +} + +pub(crate) type CommandSender = Sender; +pub(crate) type CommandReceiver = Receiver; +pub(crate) type Ret = oneshot::Sender>; + +pub enum Command { + ComposeTransaction { transaction: Transaction, ret: Ret<()> }, + GetDocumentContent { pretty: bool, ret: Ret }, +} diff --git a/frontend/rust-lib/flowy-document/src/entities.rs b/frontend/rust-lib/flowy-document/src/entities.rs index b84b4f8201..4fadde6d7f 100644 --- a/frontend/rust-lib/flowy-document/src/entities.rs +++ b/frontend/rust-lib/flowy-document/src/entities.rs @@ -9,13 +9,13 @@ pub enum ExportType { Link = 2, } -impl std::default::Default for ExportType { +impl Default for ExportType { fn default() -> Self { ExportType::Text } } -impl std::convert::From for ExportType { +impl From for ExportType { fn from(val: i32) -> Self { match val { 0 => ExportType::Text, @@ -37,10 +37,6 @@ pub struct EditPayloadPB { // Encode in JSON format #[pb(index = 2)] pub operations: String, - - // Encode in JSON format - #[pb(index = 3)] - pub operations_str: String, } #[derive(Default)] @@ -49,9 +45,6 @@ pub struct EditParams { // Encode in JSON format pub operations: String, - - // Encode in JSON format - pub operations_str: String, } impl TryInto for EditPayloadPB { @@ -60,7 +53,6 @@ impl TryInto for EditPayloadPB { Ok(EditParams { doc_id: self.doc_id, operations: self.operations, - operations_str: self.operations_str, }) } } @@ -82,12 +74,41 @@ pub struct ExportPayloadPB { #[pb(index = 2)] pub export_type: ExportType, + + #[pb(index = 3)] + pub document_version: DocumentVersionPB, +} + +#[derive(PartialEq, Debug, ProtoBuf_Enum, Clone)] +pub enum DocumentVersionPB { + /// this version's content of the document is build from `Delta`. It uses + /// `DeltaDocumentEditor`. + V0 = 0, + /// this version's content of the document is build from `NodeTree`. It uses + /// `AppFlowyDocumentEditor` + V1 = 1, +} + +impl std::default::Default for DocumentVersionPB { + fn default() -> Self { + Self::V0 + } +} + +#[derive(Default, ProtoBuf)] +pub struct OpenDocumentContextPB { + #[pb(index = 1)] + pub document_id: String, + + #[pb(index = 2)] + pub document_version: DocumentVersionPB, } #[derive(Default, Debug)] pub struct ExportParams { pub view_id: String, pub export_type: ExportType, + pub document_version: DocumentVersionPB, } impl TryInto for ExportPayloadPB { @@ -96,6 +117,7 @@ impl TryInto for ExportPayloadPB { Ok(ExportParams { view_id: self.view_id, export_type: self.export_type, + document_version: self.document_version, }) } } diff --git a/frontend/rust-lib/flowy-document/src/event_handler.rs b/frontend/rust-lib/flowy-document/src/event_handler.rs index 398f3c3747..2d8d99968b 100644 --- a/frontend/rust-lib/flowy-document/src/event_handler.rs +++ b/frontend/rust-lib/flowy-document/src/event_handler.rs @@ -1,21 +1,23 @@ -use crate::entities::{DocumentSnapshotPB, EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB}; +use crate::entities::{ + DocumentSnapshotPB, EditParams, EditPayloadPB, ExportDataPB, ExportParams, ExportPayloadPB, OpenDocumentContextPB, +}; use crate::DocumentManager; use flowy_error::FlowyError; -use flowy_sync::entities::document::DocumentIdPB; + use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::convert::TryInto; use std::sync::Arc; pub(crate) async fn get_document_handler( - data: Data, + data: Data, manager: AppData>, ) -> DataResult { - let document_id: DocumentIdPB = data.into_inner(); - let editor = manager.open_document_editor(&document_id).await?; - let operations_str = editor.get_operation_str().await?; + let context: OpenDocumentContextPB = data.into_inner(); + let editor = manager.open_document_editor(&context.document_id).await?; + let document_data = editor.export().await?; data_result(DocumentSnapshotPB { - doc_id: document_id.into(), - snapshot: operations_str, + doc_id: context.document_id, + snapshot: document_data, }) } @@ -35,9 +37,9 @@ pub(crate) async fn export_handler( ) -> DataResult { let params: ExportParams = data.into_inner().try_into()?; let editor = manager.open_document_editor(¶ms.view_id).await?; - let operations_str = editor.get_operation_str().await?; + let document_data = editor.export().await?; data_result(ExportDataPB { - data: operations_str, + data: document_data, export_type: params.export_type, }) } diff --git a/frontend/rust-lib/flowy-document/src/event_map.rs b/frontend/rust-lib/flowy-document/src/event_map.rs index 71042cad03..50aa9cf7cf 100644 --- a/frontend/rust-lib/flowy-document/src/event_map.rs +++ b/frontend/rust-lib/flowy-document/src/event_map.rs @@ -19,7 +19,7 @@ pub fn create(document_manager: Arc) -> Module { #[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)] #[event_err = "FlowyError"] pub enum DocumentEvent { - #[event(input = "DocumentIdPB", output = "DocumentSnapshotPB")] + #[event(input = "OpenDocumentContextPB", output = "DocumentSnapshotPB")] GetDocument = 0, #[event(input = "EditPayloadPB")] diff --git a/frontend/rust-lib/flowy-document/src/lib.rs b/frontend/rust-lib/flowy-document/src/lib.rs index 399ac9a342..39e46f538e 100644 --- a/frontend/rust-lib/flowy-document/src/lib.rs +++ b/frontend/rust-lib/flowy-document/src/lib.rs @@ -1,12 +1,13 @@ -pub mod editor; -mod entities; +pub mod entities; mod event_handler; pub mod event_map; pub mod manager; -mod queue; -mod web_socket; +pub mod editor; +pub mod old_editor; pub mod protobuf; +mod services; + pub use manager::*; pub mod errors { pub use flowy_error::{internal_error, ErrorCode, FlowyError}; @@ -15,7 +16,7 @@ pub mod errors { pub const TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS: u64 = 1000; use crate::errors::FlowyError; -use flowy_sync::entities::document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}; +use flowy_http_model::document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}; use lib_infra::future::FutureResult; pub trait DocumentCloudService: Send + Sync { diff --git a/frontend/rust-lib/flowy-document/src/manager.rs b/frontend/rust-lib/flowy-document/src/manager.rs index a5597305f8..ba7150889d 100644 --- a/frontend/rust-lib/flowy-document/src/manager.rs +++ b/frontend/rust-lib/flowy-document/src/manager.rs @@ -1,100 +1,149 @@ -use crate::editor::DocumentRevisionCompactor; -use crate::entities::EditParams; -use crate::{editor::DocumentEditor, errors::FlowyError, DocumentCloudService}; +use crate::editor::{initial_document_content, AppFlowyDocumentEditor, DocumentRevisionCompress}; +use crate::entities::{DocumentVersionPB, EditParams}; +use crate::old_editor::editor::{DeltaDocumentEditor, DeltaDocumentRevisionCompress}; +use crate::services::rev_sqlite::{SQLiteDeltaDocumentRevisionPersistence, SQLiteDocumentRevisionPersistence}; +use crate::services::DocumentPersistence; +use crate::{errors::FlowyError, DocumentCloudService}; use bytes::Bytes; -use dashmap::DashMap; + use flowy_database::ConnectionPool; use flowy_error::FlowyResult; -use flowy_revision::disk::SQLiteDocumentRevisionPersistence; +use flowy_http_model::util::md5; +use flowy_http_model::{document::DocumentIdPB, revision::Revision, ws_data::ServerRevisionWSData}; use flowy_revision::{ - RevisionCloudService, RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence, -}; -use flowy_sync::entities::{ - document::{DocumentIdPB, DocumentOperationsPB}, - revision::{md5, RepeatedRevision, Revision}, - ws_data::ServerRevisionWSData, + RevisionCloudService, RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, + SQLiteRevisionSnapshotPersistence, }; +use flowy_sync::client_document::initial_delta_document_content; use lib_infra::future::FutureResult; +use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; +use lib_ws::WSConnectState; +use std::any::Any; use std::{convert::TryInto, sync::Arc}; +use tokio::sync::RwLock; pub trait DocumentUser: Send + Sync { fn user_dir(&self) -> Result; fn user_id(&self) -> Result; fn token(&self) -> Result; +} + +pub trait DocumentDatabase: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; } +pub trait DocumentEditor: Send + Sync { + /// Called when the document get closed + fn close(&self); + + /// Exports the document content. The content is encoded in the corresponding + /// editor data format. + fn export(&self) -> FutureResult; + + /// Duplicate the document inner data into String + fn duplicate(&self) -> FutureResult; + + fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError>; + + fn receive_ws_state(&self, state: &WSConnectState); + + /// Receives the local operations made by the user input. The operations are encoded + /// in binary format. + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError>; + + /// Returns the `Any` reference that can be used to downcast back to the original, + /// concrete type. + /// + /// The indirection through `as_any` is because using `downcast_ref` + /// on `Box` *directly* only lets us downcast back to `&A` again. You can take a look at [this](https://stackoverflow.com/questions/33687447/how-to-get-a-reference-to-a-concrete-type-from-a-trait-object) + /// for more information. + /// + /// + fn as_any(&self) -> &dyn Any; +} + +#[derive(Clone, Debug)] +pub struct DocumentConfig { + pub version: DocumentVersionPB, +} + +impl std::default::Default for DocumentConfig { + fn default() -> Self { + Self { + version: DocumentVersionPB::V1, + } + } +} + pub struct DocumentManager { cloud_service: Arc, rev_web_socket: Arc, - editor_map: Arc, + editor_map: Arc>>, user: Arc, + persistence: Arc, + #[allow(dead_code)] + config: DocumentConfig, } impl DocumentManager { pub fn new( cloud_service: Arc, document_user: Arc, + database: Arc, rev_web_socket: Arc, + config: DocumentConfig, ) -> Self { Self { cloud_service, rev_web_socket, - editor_map: Arc::new(DocumentEditorMap::new()), + editor_map: Arc::new(RwLock::new(RefCountHashMap::new())), user: document_user, + persistence: Arc::new(DocumentPersistence::new(database)), + config, } } - pub fn init(&self) -> FlowyResult<()> { + /// Called immediately after the application launched with the user sign in/sign up. + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn initialize(&self, user_id: &str) -> FlowyResult<()> { + let _ = self.persistence.initialize(user_id)?; listen_ws_state_changed(self.rev_web_socket.clone(), self.editor_map.clone()); - Ok(()) } - #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] - pub async fn open_document_editor>(&self, editor_id: T) -> Result, FlowyError> { - let editor_id = editor_id.as_ref(); - tracing::Span::current().record("editor_id", &editor_id); - self.get_document_editor(editor_id).await - } - - #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] - pub fn close_document_editor>(&self, editor_id: T) -> Result<(), FlowyError> { - let editor_id = editor_id.as_ref(); - tracing::Span::current().record("editor_id", &editor_id); - self.editor_map.remove(editor_id); + pub async fn initialize_with_new_user(&self, _user_id: &str, _token: &str) -> FlowyResult<()> { Ok(()) } - #[tracing::instrument(level = "debug", skip(self, payload), err)] - pub async fn receive_local_operations( + #[tracing::instrument(level = "trace", skip_all, fields(document_id), err)] + pub async fn open_document_editor>( &self, - payload: DocumentOperationsPB, - ) -> Result { - let editor = self.get_document_editor(&payload.doc_id).await?; - let _ = editor - .compose_local_operations(Bytes::from(payload.operations_str)) - .await?; - let operations_str = editor.get_operation_str().await?; - Ok(DocumentOperationsPB { - doc_id: payload.doc_id.clone(), - operations_str, - }) + document_id: T, + ) -> Result, FlowyError> { + let document_id = document_id.as_ref(); + tracing::Span::current().record("document_id", &document_id); + self.init_document_editor(document_id).await + } + + #[tracing::instrument(level = "trace", skip(self, editor_id), fields(editor_id), err)] + pub async fn close_document_editor>(&self, editor_id: T) -> Result<(), FlowyError> { + let editor_id = editor_id.as_ref(); + tracing::Span::current().record("editor_id", &editor_id); + self.editor_map.write().await.remove(editor_id); + Ok(()) } pub async fn apply_edit(&self, params: EditParams) -> FlowyResult<()> { let editor = self.get_document_editor(¶ms.doc_id).await?; - let _ = editor - .compose_local_operations(Bytes::from(params.operations_str)) - .await?; + let _ = editor.compose_local_operations(Bytes::from(params.operations)).await?; Ok(()) } - pub async fn create_document>(&self, doc_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { + pub async fn create_document>(&self, doc_id: T, revisions: Vec) -> FlowyResult<()> { let doc_id = doc_id.as_ref().to_owned(); - let db_pool = self.user.db_pool()?; + let db_pool = self.persistence.database.db_pool()?; // Maybe we could save the document to disk without creating the RevisionManager - let rev_manager = self.make_document_rev_manager(&doc_id, db_pool)?; + let rev_manager = self.make_rev_manager(&doc_id, db_pool)?; let _ = rev_manager.reset_object(revisions).await?; Ok(()) } @@ -102,9 +151,9 @@ impl DocumentManager { pub async fn receive_ws_data(&self, data: Bytes) { let result: Result = data.try_into(); match result { - Ok(data) => match self.editor_map.get(&data.object_id) { + Ok(data) => match self.editor_map.read().await.get(&data.object_id) { None => tracing::error!("Can't find any source handler for {:?}-{:?}", data.object_id, data.ty), - Some(editor) => match editor.receive_ws_data(data).await { + Some(handler) => match handler.0.receive_ws_data(data).await { Ok(_) => {} Err(e) => tracing::error!("{}", e), }, @@ -114,12 +163,17 @@ impl DocumentManager { } } } + + pub fn initial_document_content(&self) -> String { + match self.config.version { + DocumentVersionPB::V0 => initial_delta_document_content(), + DocumentVersionPB::V1 => initial_document_content(), + } + } } impl DocumentManager { /// Returns the `DocumentEditor` - /// Initializes the document editor if it's not initialized yet. Otherwise, returns the opened - /// editor. /// /// # Arguments /// @@ -127,13 +181,14 @@ impl DocumentManager { /// /// returns: Result, FlowyError> /// - async fn get_document_editor(&self, doc_id: &str) -> FlowyResult> { - match self.editor_map.get(doc_id) { + async fn get_document_editor(&self, doc_id: &str) -> FlowyResult> { + match self.editor_map.read().await.get(doc_id) { None => { - let db_pool = self.user.db_pool()?; - self.init_document_editor(doc_id, db_pool).await + // + tracing::warn!("Should call init_document_editor first"); + self.init_document_editor(doc_id).await } - Some(editor) => Ok(editor), + Some(handler) => Ok(handler.0.clone()), } } @@ -146,41 +201,90 @@ impl DocumentManager { /// /// returns: Result, FlowyError> /// - #[tracing::instrument(level = "trace", skip(self, pool), err)] - async fn init_document_editor( - &self, - doc_id: &str, - pool: Arc, - ) -> Result, FlowyError> { + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn init_document_editor(&self, doc_id: &str) -> Result, FlowyError> { + let pool = self.persistence.database.db_pool()?; let user = self.user.clone(); let token = self.user.token()?; - let rev_manager = self.make_document_rev_manager(doc_id, pool.clone())?; let cloud_service = Arc::new(DocumentRevisionCloudService { token, server: self.cloud_service.clone(), }); - let editor = DocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service).await?; - self.editor_map.insert(doc_id, &editor); - Ok(editor) + + match self.config.version { + DocumentVersionPB::V0 => { + let rev_manager = self.make_delta_document_rev_manager(doc_id, pool.clone())?; + let editor: Arc = Arc::new( + DeltaDocumentEditor::new(doc_id, user, rev_manager, self.rev_web_socket.clone(), cloud_service) + .await?, + ); + self.editor_map + .write() + .await + .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); + Ok(editor) + } + DocumentVersionPB::V1 => { + let rev_manager = self.make_document_rev_manager(doc_id, pool.clone())?; + let editor: Arc = + Arc::new(AppFlowyDocumentEditor::new(doc_id, user, rev_manager, cloud_service).await?); + self.editor_map + .write() + .await + .insert(doc_id.to_string(), RefCountDocumentHandler(editor.clone())); + Ok(editor) + } + } + } + + fn make_rev_manager( + &self, + doc_id: &str, + pool: Arc, + ) -> Result>, FlowyError> { + match self.config.version { + DocumentVersionPB::V0 => self.make_delta_document_rev_manager(doc_id, pool), + DocumentVersionPB::V1 => self.make_document_rev_manager(doc_id, pool), + } } fn make_document_rev_manager( &self, doc_id: &str, pool: Arc, - ) -> Result { + ) -> Result>, FlowyError> { let user_id = self.user.user_id()?; let disk_cache = SQLiteDocumentRevisionPersistence::new(&user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache); + let configuration = RevisionPersistenceConfiguration::new(100, true); + let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); // let history_persistence = SQLiteRevisionHistoryPersistence::new(doc_id, pool.clone()); let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(doc_id, pool); - let rev_compactor = DocumentRevisionCompactor(); - Ok(RevisionManager::new( &user_id, doc_id, rev_persistence, - rev_compactor, + DocumentRevisionCompress(), + // history_persistence, + snapshot_persistence, + )) + } + + fn make_delta_document_rev_manager( + &self, + doc_id: &str, + pool: Arc, + ) -> Result>, FlowyError> { + let user_id = self.user.user_id()?; + let disk_cache = SQLiteDeltaDocumentRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(100, true); + let rev_persistence = RevisionPersistence::new(&user_id, doc_id, disk_cache, configuration); + // let history_persistence = SQLiteRevisionHistoryPersistence::new(doc_id, pool.clone()); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(doc_id, pool); + Ok(RevisionManager::new( + &user_id, + doc_id, + rev_persistence, + DeltaDocumentRevisionCompress(), // history_persistence, snapshot_persistence, )) @@ -198,22 +302,14 @@ impl RevisionCloudService for DocumentRevisionCloudService { let params: DocumentIdPB = object_id.to_string().into(); let server = self.server.clone(); let token = self.token.clone(); - let user_id = user_id.to_string(); FutureResult::new(async move { match server.fetch_document(&token, params).await? { None => Err(FlowyError::record_not_found().context("Remote doesn't have this document")), Some(payload) => { - let bytes = Bytes::from(payload.content.clone()); + let bytes = Bytes::from(payload.data.clone()); let doc_md5 = md5(&bytes); - let revision = Revision::new( - &payload.doc_id, - payload.base_rev_id, - payload.rev_id, - bytes, - &user_id, - doc_md5, - ); + let revision = Revision::new(&payload.doc_id, payload.base_rev_id, payload.rev_id, bytes, doc_md5); Ok(vec![revision]) } } @@ -221,40 +317,32 @@ impl RevisionCloudService for DocumentRevisionCloudService { } } -pub struct DocumentEditorMap { - inner: DashMap>, +#[derive(Clone)] +struct RefCountDocumentHandler(Arc); + +impl RefCountValue for RefCountDocumentHandler { + fn did_remove(&self) { + self.0.close(); + } } -impl DocumentEditorMap { - fn new() -> Self { - Self { inner: DashMap::new() } - } +impl std::ops::Deref for RefCountDocumentHandler { + type Target = Arc; - pub(crate) fn insert(&self, editor_id: &str, doc: &Arc) { - if self.inner.contains_key(editor_id) { - log::warn!("Doc:{} already exists in cache", editor_id); - } - self.inner.insert(editor_id.to_string(), doc.clone()); - } - - pub(crate) fn get(&self, editor_id: &str) -> Option> { - Some(self.inner.get(editor_id)?.clone()) - } - - pub(crate) fn remove(&self, editor_id: &str) { - if let Some(editor) = self.get(editor_id) { - editor.stop() - } - self.inner.remove(editor_id); + fn deref(&self) -> &Self::Target { + &self.0 } } #[tracing::instrument(level = "trace", skip(web_socket, handlers))] -fn listen_ws_state_changed(web_socket: Arc, handlers: Arc) { +fn listen_ws_state_changed( + web_socket: Arc, + handlers: Arc>>, +) { tokio::spawn(async move { let mut notify = web_socket.subscribe_state_changed().await; while let Ok(state) = notify.recv().await { - handlers.inner.iter().for_each(|handler| { + handlers.read().await.values().iter().for_each(|handler| { handler.receive_ws_state(&state); }) } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs b/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/old_editor/conflict.rs @@ -0,0 +1 @@ + diff --git a/frontend/rust-lib/flowy-document/src/editor.rs b/frontend/rust-lib/flowy-document/src/old_editor/editor.rs similarity index 63% rename from frontend/rust-lib/flowy-document/src/editor.rs rename to frontend/rust-lib/flowy-document/src/old_editor/editor.rs index 7712269bfe..75d67b2be1 100644 --- a/frontend/rust-lib/flowy-document/src/editor.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/editor.rs @@ -1,57 +1,58 @@ -use crate::web_socket::EditorCommandSender; -use crate::{ - errors::FlowyError, - queue::{EditDocumentQueue, EditorCommand}, - DocumentUser, -}; +#![allow(unused_attributes)] +#![allow(unused_attributes)] +use crate::old_editor::queue::{EditDocumentQueue, EditorCommand, EditorCommandSender}; +use crate::{errors::FlowyError, DocumentEditor, DocumentUser}; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{internal_error, FlowyResult}; +use flowy_http_model::document::DocumentPayloadPB; +use flowy_http_model::revision::Revision; +use flowy_http_model::ws_data::ServerRevisionWSData; use flowy_revision::{ - RevisionCloudService, RevisionCompress, RevisionManager, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, RevisionWebSocket, }; -use flowy_sync::entities::ws_data::ServerRevisionWSData; -use flowy_sync::{ - entities::{document::DocumentPayloadPB, revision::Revision}, - errors::CollaborateResult, - util::make_operations_from_revisions, -}; +use flowy_sync::{errors::CollaborateResult, util::make_operations_from_revisions}; +use lib_infra::future::FutureResult; use lib_ot::core::{AttributeEntry, AttributeHashMap}; use lib_ot::{ core::{DeltaOperation, Interval}, - text_delta::TextOperations, + text_delta::DeltaTextOperations, }; use lib_ws::WSConnectState; +use std::any::Any; use std::sync::Arc; use tokio::sync::{mpsc, oneshot}; -pub struct DocumentEditor { +pub struct DeltaDocumentEditor { pub doc_id: String, #[allow(dead_code)] - rev_manager: Arc, + rev_manager: Arc>>, #[cfg(feature = "sync")] ws_manager: Arc, edit_cmd_tx: EditorCommandSender, } -impl DocumentEditor { +impl DeltaDocumentEditor { #[allow(unused_variables)] pub(crate) async fn new( doc_id: &str, user: Arc, - mut rev_manager: RevisionManager, + mut rev_manager: RevisionManager>, rev_web_socket: Arc, cloud_service: Arc, ) -> FlowyResult> { - let document_info = rev_manager.load::(Some(cloud_service)).await?; - let operations = TextOperations::from_bytes(&document_info.content)?; + let document = rev_manager + .initialize::(Some(cloud_service)) + .await?; + let operations = DeltaTextOperations::from_bytes(&document.data)?; let rev_manager = Arc::new(rev_manager); let doc_id = doc_id.to_string(); let user_id = user.user_id()?; let edit_cmd_tx = spawn_edit_queue(user, rev_manager.clone(), operations); #[cfg(feature = "sync")] - let ws_manager = crate::web_socket::make_document_ws_manager( + let ws_manager = crate::old_editor::web_socket::make_document_ws_manager( doc_id.clone(), user_id.clone(), edit_cmd_tx.clone(), @@ -142,51 +143,64 @@ impl DocumentEditor { let _ = rx.await.map_err(internal_error)??; Ok(()) } +} - pub async fn get_operation_str(&self) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::StringifyOperations { ret }; - let _ = self.edit_cmd_tx.send(msg).await; - let json = rx.await.map_err(internal_error)??; - Ok(json) - } - - #[tracing::instrument(level = "trace", skip(self, data), err)] - pub(crate) async fn compose_local_operations(&self, data: Bytes) -> Result<(), FlowyError> { - let operations = TextOperations::from_bytes(&data)?; - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::ComposeLocalOperations { operations, ret }; - let _ = self.edit_cmd_tx.send(msg).await; - let _ = rx.await.map_err(internal_error)??; - Ok(()) - } - - #[cfg(feature = "sync")] - pub fn stop(&self) { +impl DocumentEditor for Arc { + fn close(&self) { + #[cfg(feature = "sync")] self.ws_manager.stop(); } - #[cfg(not(feature = "sync"))] - pub fn stop(&self) {} - - #[cfg(feature = "sync")] - pub(crate) async fn receive_ws_data(&self, data: ServerRevisionWSData) -> Result<(), FlowyError> { - self.ws_manager.receive_ws_data(data).await - } - #[cfg(not(feature = "sync"))] - pub(crate) async fn receive_ws_data(&self, _data: ServerRevisionWSData) -> Result<(), FlowyError> { - Ok(()) + fn export(&self) -> FutureResult { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::GetOperationsString { ret }; + let edit_cmd_tx = self.edit_cmd_tx.clone(); + FutureResult::new(async move { + let _ = edit_cmd_tx.send(msg).await; + let json = rx.await.map_err(internal_error)??; + Ok(json) + }) } - #[cfg(feature = "sync")] - pub(crate) fn receive_ws_state(&self, state: &WSConnectState) { + fn duplicate(&self) -> FutureResult { + self.export() + } + + #[allow(unused_variables)] + fn receive_ws_data(&self, data: ServerRevisionWSData) -> FutureResult<(), FlowyError> { + let cloned_self = self.clone(); + FutureResult::new(async move { + #[cfg(feature = "sync")] + let _ = cloned_self.ws_manager.receive_ws_data(data).await?; + + Ok(()) + }) + } + + #[allow(unused_variables)] + fn receive_ws_state(&self, state: &WSConnectState) { + #[cfg(feature = "sync")] self.ws_manager.connect_state_changed(state.clone()); } - #[cfg(not(feature = "sync"))] - pub(crate) fn receive_ws_state(&self, _state: &WSConnectState) {} -} -impl std::ops::Drop for DocumentEditor { + fn compose_local_operations(&self, data: Bytes) -> FutureResult<(), FlowyError> { + let edit_cmd_tx = self.edit_cmd_tx.clone(); + FutureResult::new(async move { + let operations = DeltaTextOperations::from_bytes(&data)?; + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::ComposeLocalOperations { operations, ret }; + + let _ = edit_cmd_tx.send(msg).await; + let _ = rx.await.map_err(internal_error)??; + Ok(()) + }) + } + + fn as_any(&self) -> &dyn Any { + self + } +} +impl std::ops::Drop for DeltaDocumentEditor { fn drop(&mut self) { tracing::trace!("{} DocumentEditor was dropped", self.doc_id) } @@ -195,8 +209,8 @@ impl std::ops::Drop for DocumentEditor { // The edit queue will exit after the EditorCommandSender was dropped. fn spawn_edit_queue( user: Arc, - rev_manager: Arc, - delta: TextOperations, + rev_manager: Arc>>, + delta: DeltaTextOperations, ) -> EditorCommandSender { let (sender, receiver) = mpsc::channel(1000); let edit_queue = EditDocumentQueue::new(user, rev_manager, delta, receiver); @@ -214,22 +228,22 @@ fn spawn_edit_queue( } #[cfg(feature = "flowy_unit_test")] -impl DocumentEditor { - pub async fn document_operations(&self) -> FlowyResult { - let (ret, rx) = oneshot::channel::>(); - let msg = EditorCommand::ReadOperations { ret }; +impl DeltaDocumentEditor { + pub async fn document_operations(&self) -> FlowyResult { + let (ret, rx) = oneshot::channel::>(); + let msg = EditorCommand::GetOperations { ret }; let _ = self.edit_cmd_tx.send(msg).await; let delta = rx.await.map_err(internal_error)??; Ok(delta) } - pub fn rev_manager(&self) -> Arc { + pub fn rev_manager(&self) -> Arc>> { self.rev_manager.clone() } } -pub struct DocumentRevisionSerde(); -impl RevisionObjectDeserializer for DocumentRevisionSerde { +pub struct DeltaDocumentRevisionSerde(); +impl RevisionObjectDeserializer for DeltaDocumentRevisionSerde { type Output = DocumentPayloadPB; fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { @@ -239,30 +253,30 @@ impl RevisionObjectDeserializer for DocumentRevisionSerde { Result::::Ok(DocumentPayloadPB { doc_id: object_id.to_owned(), - content: delta.json_str(), + data: delta.json_bytes().to_vec(), rev_id, base_rev_id, }) } } -impl RevisionObjectSerializer for DocumentRevisionSerde { - fn serialize_revisions(revisions: Vec) -> FlowyResult { +impl RevisionObjectSerializer for DeltaDocumentRevisionSerde { + fn combine_revisions(revisions: Vec) -> FlowyResult { let operations = make_operations_from_revisions::(revisions)?; Ok(operations.json_bytes()) } } -pub(crate) struct DocumentRevisionCompactor(); -impl RevisionCompress for DocumentRevisionCompactor { - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult { - DocumentRevisionSerde::serialize_revisions(revisions) +pub(crate) struct DeltaDocumentRevisionCompress(); +impl RevisionMergeable for DeltaDocumentRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + DeltaDocumentRevisionSerde::combine_revisions(revisions) } } // quill-editor requires the delta should end with '\n' and only contains the // insert operation. The function, correct_delta maybe be removed in the future. -fn correct_delta(delta: &mut TextOperations) { +fn correct_delta(delta: &mut DeltaTextOperations) { if let Some(op) = delta.ops.last() { let op_data = op.get_data(); if !op_data.ends_with('\n') { diff --git a/frontend/rust-lib/flowy-document/src/old_editor/mod.rs b/frontend/rust-lib/flowy-document/src/old_editor/mod.rs new file mode 100644 index 0000000000..c12625cda6 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/old_editor/mod.rs @@ -0,0 +1,4 @@ +pub mod conflict; +pub mod editor; +pub mod queue; +mod web_socket; diff --git a/frontend/rust-lib/flowy-document/src/queue.rs b/frontend/rust-lib/flowy-document/src/old_editor/queue.rs similarity index 77% rename from frontend/rust-lib/flowy-document/src/queue.rs rename to frontend/rust-lib/flowy-document/src/old_editor/queue.rs index d74970e9e2..528cc062af 100644 --- a/frontend/rust-lib/flowy-document/src/queue.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/queue.rs @@ -1,36 +1,39 @@ -use crate::web_socket::{DocumentResolveOperations, EditorCommandReceiver}; +use crate::old_editor::web_socket::DeltaDocumentResolveOperations; use crate::DocumentUser; use async_stream::stream; +use flowy_database::ConnectionPool; use flowy_error::FlowyError; -use flowy_revision::{OperationsMD5, RevisionManager, TransformOperations}; +use flowy_http_model::revision::{RevId, Revision}; +use flowy_revision::{RevisionMD5, RevisionManager, TransformOperations}; use flowy_sync::{ client_document::{history::UndoResult, ClientDocument}, - entities::revision::{RevId, Revision}, errors::CollaborateError, }; use futures::stream::StreamExt; use lib_ot::core::AttributeEntry; use lib_ot::{ core::{Interval, OperationTransform}, - text_delta::TextOperations, + text_delta::DeltaTextOperations, }; use std::sync::Arc; +use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::{oneshot, RwLock}; // The EditorCommandQueue executes each command that will alter the document in // serial. pub(crate) struct EditDocumentQueue { document: Arc>, + #[allow(dead_code)] user: Arc, - rev_manager: Arc, + rev_manager: Arc>>, receiver: Option, } impl EditDocumentQueue { pub(crate) fn new( user: Arc, - rev_manager: Arc, - operations: TextOperations, + rev_manager: Arc>>, + operations: DeltaTextOperations, receiver: EditorCommandReceiver, ) -> Self { let document = Arc::new(RwLock::new(ClientDocument::from_operations(operations))); @@ -68,7 +71,7 @@ impl EditDocumentQueue { EditorCommand::ComposeLocalOperations { operations, ret } => { let mut document = self.document.write().await; let _ = document.compose_operations(operations.clone())?; - let md5 = document.md5(); + let md5 = document.document_md5(); drop(document); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); @@ -76,22 +79,22 @@ impl EditDocumentQueue { EditorCommand::ComposeRemoteOperation { client_operations, ret } => { let mut document = self.document.write().await; let _ = document.compose_operations(client_operations.clone())?; - let md5 = document.md5(); + let md5 = document.document_md5(); drop(document); - let _ = ret.send(Ok(md5)); + let _ = ret.send(Ok(md5.into())); } EditorCommand::ResetOperations { operations, ret } => { let mut document = self.document.write().await; let _ = document.set_operations(operations); - let md5 = document.md5(); + let md5 = document.document_md5(); drop(document); - let _ = ret.send(Ok(md5)); + let _ = ret.send(Ok(md5.into())); } EditorCommand::TransformOperations { operations, ret } => { let f = || async { let read_guard = self.document.read().await; - let mut server_operations: Option = None; - let client_operations: TextOperations; + let mut server_operations: Option = None; + let client_operations: DeltaTextOperations; if read_guard.is_empty() { // Do nothing @@ -99,11 +102,11 @@ impl EditDocumentQueue { } else { let (s_prime, c_prime) = read_guard.get_operations().transform(&operations)?; client_operations = c_prime; - server_operations = Some(DocumentResolveOperations(s_prime)); + server_operations = Some(DeltaDocumentResolveOperations(s_prime)); } drop(read_guard); Ok::(TransformOperations { - client_operations: DocumentResolveOperations(client_operations), + client_operations: DeltaDocumentResolveOperations(client_operations), server_operations, }) }; @@ -112,14 +115,14 @@ impl EditDocumentQueue { EditorCommand::Insert { index, data, ret } => { let mut write_guard = self.document.write().await; let operations = write_guard.insert(index, data)?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } EditorCommand::Delete { interval, ret } => { let mut write_guard = self.document.write().await; let operations = write_guard.delete(interval)?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } @@ -130,14 +133,14 @@ impl EditDocumentQueue { } => { let mut write_guard = self.document.write().await; let operations = write_guard.format(interval, attribute)?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } EditorCommand::Replace { interval, data, ret } => { let mut write_guard = self.document.write().await; let operations = write_guard.replace(interval, data)?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } @@ -150,22 +153,22 @@ impl EditDocumentQueue { EditorCommand::Undo { ret } => { let mut write_guard = self.document.write().await; let UndoResult { operations } = write_guard.undo()?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } EditorCommand::Redo { ret } => { let mut write_guard = self.document.write().await; let UndoResult { operations } = write_guard.redo()?; - let md5 = write_guard.md5(); + let md5 = write_guard.document_md5(); let _ = self.save_local_operations(operations, md5).await?; let _ = ret.send(Ok(())); } - EditorCommand::StringifyOperations { ret } => { + EditorCommand::GetOperationsString { ret } => { let data = self.document.read().await.get_operations_json(); let _ = ret.send(Ok(data)); } - EditorCommand::ReadOperations { ret } => { + EditorCommand::GetOperations { ret } => { let operations = self.document.read().await.get_operations().clone(); let _ = ret.send(Ok(operations)); } @@ -173,35 +176,35 @@ impl EditDocumentQueue { Ok(()) } - async fn save_local_operations(&self, operations: TextOperations, md5: String) -> Result { + async fn save_local_operations(&self, operations: DeltaTextOperations, md5: String) -> Result { let bytes = operations.json_bytes(); let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); - let user_id = self.user.user_id()?; - let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, bytes, &user_id, md5); + let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, bytes, md5); let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(rev_id.into()) } } -pub type TextTransformOperations = TransformOperations; - +pub type TextTransformOperations = TransformOperations; +pub(crate) type EditorCommandSender = Sender; +pub(crate) type EditorCommandReceiver = Receiver; pub(crate) type Ret = oneshot::Sender>; pub(crate) enum EditorCommand { ComposeLocalOperations { - operations: TextOperations, + operations: DeltaTextOperations, ret: Ret<()>, }, ComposeRemoteOperation { - client_operations: TextOperations, - ret: Ret, + client_operations: DeltaTextOperations, + ret: Ret, }, ResetOperations { - operations: TextOperations, - ret: Ret, + operations: DeltaTextOperations, + ret: Ret, }, TransformOperations { - operations: TextOperations, + operations: DeltaTextOperations, ret: Ret, }, Insert { @@ -235,12 +238,12 @@ pub(crate) enum EditorCommand { Redo { ret: Ret<()>, }, - StringifyOperations { + GetOperationsString { ret: Ret, }, #[allow(dead_code)] - ReadOperations { - ret: Ret, + GetOperations { + ret: Ret, }, } @@ -259,8 +262,8 @@ impl std::fmt::Debug for EditorCommand { EditorCommand::CanRedo { .. } => "CanRedo", EditorCommand::Undo { .. } => "Undo", EditorCommand::Redo { .. } => "Redo", - EditorCommand::StringifyOperations { .. } => "StringifyOperations", - EditorCommand::ReadOperations { .. } => "ReadOperations", + EditorCommand::GetOperationsString { .. } => "StringifyOperations", + EditorCommand::GetOperations { .. } => "ReadOperations", }; f.write_str(s) } diff --git a/frontend/rust-lib/flowy-document/src/old_editor/util.rs b/frontend/rust-lib/flowy-document/src/old_editor/util.rs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/rust-lib/flowy-document/src/web_socket.rs b/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs similarity index 77% rename from frontend/rust-lib/flowy-document/src/web_socket.rs rename to frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs index 8c4e1170af..f10a8e64be 100644 --- a/frontend/rust-lib/flowy-document/src/web_socket.rs +++ b/frontend/rust-lib/flowy-document/src/old_editor/web_socket.rs @@ -1,60 +1,52 @@ -use crate::queue::TextTransformOperations; -use crate::{queue::EditorCommand, TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS}; +use crate::old_editor::queue::{EditorCommand, EditorCommandSender, TextTransformOperations}; +use crate::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_revision::*; -use flowy_sync::entities::revision::Revision; -use flowy_sync::{ - entities::{ - revision::RevisionRange, - ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType}, - }, - errors::CollaborateResult, +use flowy_http_model::{ + revision::{Revision, RevisionRange}, + ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType}, }; -use lib_infra::future::{BoxResultFuture, FutureResult}; - +use flowy_revision::*; +use flowy_sync::errors::CollaborateResult; use flowy_sync::util::make_operations_from_revisions; -use lib_ot::text_delta::TextOperations; +use lib_infra::future::{BoxResultFuture, FutureResult}; +use lib_ot::text_delta::DeltaTextOperations; use lib_ws::WSConnectState; use std::{sync::Arc, time::Duration}; -use tokio::sync::{ - broadcast, - mpsc::{Receiver, Sender}, - oneshot, -}; - -pub(crate) type EditorCommandSender = Sender; -pub(crate) type EditorCommandReceiver = Receiver; +use tokio::sync::{broadcast, oneshot}; #[derive(Clone)] -pub struct DocumentResolveOperations(pub TextOperations); +pub struct DeltaDocumentResolveOperations(pub DeltaTextOperations); -impl OperationsDeserializer for DocumentResolveOperations { - fn deserialize_revisions(revisions: Vec) -> FlowyResult { - Ok(DocumentResolveOperations(make_operations_from_revisions(revisions)?)) +impl OperationsDeserializer for DeltaDocumentResolveOperations { + fn deserialize_revisions(revisions: Vec) -> FlowyResult { + Ok(DeltaDocumentResolveOperations(make_operations_from_revisions( + revisions, + )?)) } } -impl OperationsSerializer for DocumentResolveOperations { +impl OperationsSerializer for DeltaDocumentResolveOperations { fn serialize_operations(&self) -> Bytes { self.0.json_bytes() } } -impl DocumentResolveOperations { - pub fn into_inner(self) -> TextOperations { +impl DeltaDocumentResolveOperations { + pub fn into_inner(self) -> DeltaTextOperations { self.0 } } -pub type DocumentConflictController = ConflictController; +pub type DocumentConflictController = ConflictController>; #[allow(dead_code)] pub(crate) async fn make_document_ws_manager( doc_id: String, user_id: String, edit_cmd_tx: EditorCommandSender, - rev_manager: Arc, + rev_manager: Arc>>, rev_web_socket: Arc, ) -> Arc { let ws_data_provider = Arc::new(WSDataProvider::new(&doc_id, Arc::new(rev_manager.clone()))); @@ -137,8 +129,11 @@ struct DocumentConflictResolver { edit_cmd_tx: EditorCommandSender, } -impl ConflictResolver for DocumentConflictResolver { - fn compose_operations(&self, operations: DocumentResolveOperations) -> BoxResultFuture { +impl ConflictResolver for DocumentConflictResolver { + fn compose_operations( + &self, + operations: DeltaDocumentResolveOperations, + ) -> BoxResultFuture { let tx = self.edit_cmd_tx.clone(); let operations = operations.into_inner(); Box::pin(async move { @@ -158,8 +153,8 @@ impl ConflictResolver for DocumentConflictResolver { fn transform_operations( &self, - operations: DocumentResolveOperations, - ) -> BoxResultFuture, FlowyError> { + operations: DeltaDocumentResolveOperations, + ) -> BoxResultFuture, FlowyError> { let tx = self.edit_cmd_tx.clone(); let operations = operations.into_inner(); Box::pin(async move { @@ -174,7 +169,7 @@ impl ConflictResolver for DocumentConflictResolver { }) } - fn reset_operations(&self, operations: DocumentResolveOperations) -> BoxResultFuture { + fn reset_operations(&self, operations: DeltaDocumentResolveOperations) -> BoxResultFuture { let tx = self.edit_cmd_tx.clone(); let operations = operations.into_inner(); Box::pin(async move { diff --git a/frontend/rust-lib/flowy-document/src/services/migration.rs b/frontend/rust-lib/flowy-document/src/services/migration.rs new file mode 100644 index 0000000000..373d9c9f4b --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/migration.rs @@ -0,0 +1,77 @@ +use crate::services::delta_migration::DeltaRevisionMigration; +use crate::services::rev_sqlite::{DeltaRevisionSql, SQLiteDocumentRevisionPersistence}; +use crate::DocumentDatabase; +use bytes::Bytes; +use flowy_database::kv::KV; +use flowy_error::FlowyResult; +use flowy_http_model::revision::Revision; +use flowy_http_model::util::md5; +use flowy_revision::disk::{RevisionDiskCache, SyncRecord}; +use flowy_sync::util::make_operations_from_revisions; +use std::sync::Arc; + +const V1_MIGRATION: &str = "DOCUMENT_V1_MIGRATION"; +pub(crate) struct DocumentMigration { + user_id: String, + database: Arc, +} + +impl DocumentMigration { + pub fn new(user_id: &str, database: Arc) -> Self { + let user_id = user_id.to_owned(); + Self { user_id, database } + } + + pub fn run_v1_migration(&self) -> FlowyResult<()> { + let key = migration_flag_key(&self.user_id, V1_MIGRATION); + if KV::get_bool(&key) { + return Ok(()); + } + + let pool = self.database.db_pool()?; + let conn = &*pool.get()?; + let disk_cache = SQLiteDocumentRevisionPersistence::new(&self.user_id, pool); + let documents = DeltaRevisionSql::read_all_documents(&self.user_id, conn)?; + tracing::debug!("[Document Migration]: try migrate {} documents", documents.len()); + for revisions in documents { + if revisions.is_empty() { + continue; + } + + let document_id = revisions.first().unwrap().object_id.clone(); + match make_operations_from_revisions(revisions) { + Ok(delta) => match DeltaRevisionMigration::run(delta) { + Ok(transaction) => { + let bytes = Bytes::from(transaction.to_bytes()?); + let md5 = format!("{:x}", md5::compute(&bytes)); + let revision = Revision::new(&document_id, 0, 1, bytes, md5); + let record = SyncRecord::new(revision); + match disk_cache.create_revision_records(vec![record]) { + Ok(_) => {} + Err(err) => { + tracing::error!("[Document Migration]: Save revisions to disk failed {:?}", err); + } + } + } + Err(err) => { + tracing::error!( + "[Document Migration]: Migrate revisions to transaction failed {:?}", + err + ); + } + }, + Err(e) => { + tracing::error!("[Document migration]: Make delta from revisions failed: {:?}", e); + } + } + } + // + + KV::set_bool(&key, true); + tracing::debug!("Run document v1 migration"); + Ok(()) + } +} +fn migration_flag_key(user_id: &str, version: &str) -> String { + md5(format!("{}{}", user_id, version,)) +} diff --git a/frontend/rust-lib/flowy-document/src/services/mod.rs b/frontend/rust-lib/flowy-document/src/services/mod.rs new file mode 100644 index 0000000000..d92b4a1a53 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/mod.rs @@ -0,0 +1,4 @@ +mod migration; +mod persistence; + +pub use persistence::*; diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs b/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs new file mode 100644 index 0000000000..eaa1aad584 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/persistence/delta_migration.rs @@ -0,0 +1,419 @@ +use crate::editor::{DocumentNode, DocumentOperation}; +use flowy_error::FlowyResult; + +use lib_ot::core::{AttributeHashMap, DeltaOperation, Insert, Transaction}; +use lib_ot::text_delta::{DeltaTextOperation, DeltaTextOperations}; + +pub struct DeltaRevisionMigration(); + +impl DeltaRevisionMigration { + pub fn run(delta: DeltaTextOperations) -> FlowyResult { + let migrate_background_attribute = |insert: &mut Insert| { + if let Some(Some(color)) = insert.attributes.get("background").map(|value| value.str_value()) { + insert.attributes.remove_key("background"); + insert.attributes.insert("backgroundColor", color); + } + }; + let migrate_strike_attribute = |insert: &mut Insert| { + if let Some(Some(_)) = insert.attributes.get("strike").map(|value| value.str_value()) { + insert.attributes.remove_key("strike"); + insert.attributes.insert("strikethrough", true); + } + }; + + let migrate_link_attribute = |insert: &mut Insert| { + if let Some(Some(link)) = insert.attributes.get("link").map(|value| value.str_value()) { + insert.attributes.remove_key("link"); + insert.attributes.insert("href", link); + } + }; + + let migrate_list_attribute = + |attribute_node: &mut DocumentNode, value: &str, number_list_number: &mut usize| { + if value == "unchecked" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "checkbox"); + attribute_node.attributes.insert("checkbox", false); + } + if value == "checked" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "checkbox"); + attribute_node.attributes.insert("checkbox", true); + } + + if value == "bullet" { + *number_list_number = 0; + attribute_node.attributes.insert("subtype", "bulleted-list"); + } + + if value == "ordered" { + *number_list_number += 1; + attribute_node.attributes.insert("subtype", "number-list"); + attribute_node.attributes.insert("number", *number_list_number); + } + }; + + let generate_new_op_with_double_new_lines = |insert: &mut Insert| { + let pattern = "\n\n"; + let mut new_ops = vec![]; + if insert.s.as_str().contains(pattern) { + let insert_str = insert.s.clone(); + let insert_strings = insert_str.split(pattern).map(|s| s.to_owned()); + for (index, new_s) in insert_strings.enumerate() { + if index == 0 { + insert.s = new_s.into(); + } else { + new_ops.push(DeltaOperation::Insert(Insert { + s: new_s.into(), + attributes: AttributeHashMap::default(), + })); + } + } + } + new_ops + }; + + let create_text_node = |ops: Vec| { + let mut document_node = DocumentNode::new(); + document_node.node_type = "text".to_owned(); + ops.into_iter().for_each(|op| document_node.delta.add(op)); + document_node + }; + + let transform_op = |mut insert: Insert| { + // Rename the attribute name from background to backgroundColor + migrate_background_attribute(&mut insert); + migrate_strike_attribute(&mut insert); + migrate_link_attribute(&mut insert); + + let new_ops = generate_new_op_with_double_new_lines(&mut insert); + (DeltaOperation::Insert(insert), new_ops) + }; + let mut index: usize = 0; + let mut number_list_number = 0; + let mut editor_node = DocumentNode::new(); + editor_node.node_type = "editor".to_owned(); + + let mut transaction = Transaction::new(); + transaction.push_operation(DocumentOperation::Insert { + path: 0.into(), + nodes: vec![editor_node], + }); + + let mut iter = delta.ops.into_iter().enumerate(); + while let Some((_, op)) = iter.next() { + let mut document_node = create_text_node(vec![]); + let mut split_document_nodes = vec![]; + match op { + DeltaOperation::Delete(_) => tracing::warn!("Should not contain delete operation"), + DeltaOperation::Retain(_) => tracing::warn!("Should not contain retain operation"), + DeltaOperation::Insert(insert) => { + if insert.s.as_str() != "\n" { + let (op, new_ops) = transform_op(insert); + document_node.delta.add(op); + if !new_ops.is_empty() { + split_document_nodes.push(create_text_node(new_ops)); + } + } + + while let Some((_, DeltaOperation::Insert(insert))) = iter.next() { + if insert.s.as_str() != "\n" { + let (op, new_ops) = transform_op(insert); + document_node.delta.add(op); + + if !new_ops.is_empty() { + split_document_nodes.push(create_text_node(new_ops)); + } + } else { + let attribute_node = match split_document_nodes.last_mut() { + None => &mut document_node, + Some(split_document_node) => split_document_node, + }; + + if let Some(value) = insert.attributes.get("header") { + attribute_node.attributes.insert("subtype", "heading"); + if let Some(v) = value.int_value() { + number_list_number = 0; + attribute_node.attributes.insert("heading", format!("h{}", v)); + } + } + + if insert.attributes.get("blockquote").is_some() { + attribute_node.attributes.insert("subtype", "quote"); + } + + if let Some(value) = insert.attributes.get("list") { + if let Some(s) = value.str_value() { + migrate_list_attribute(attribute_node, &s, &mut number_list_number); + } + } + break; + } + } + } + } + let mut operations = vec![document_node]; + operations.extend(split_document_nodes); + operations.into_iter().for_each(|node| { + // println!("{}", serde_json::to_string(&node).unwrap()); + let operation = DocumentOperation::Insert { + path: vec![0, index].into(), + nodes: vec![node], + }; + transaction.push_operation(operation); + index += 1; + }); + } + Ok(transaction) + } +} + +#[cfg(test)] +mod tests { + use crate::editor::Document; + use crate::services::delta_migration::DeltaRevisionMigration; + use lib_ot::text_delta::DeltaTextOperations; + + #[test] + fn transform_delta_to_transaction_test() { + let delta = DeltaTextOperations::from_json(DELTA_STR).unwrap(); + let transaction = DeltaRevisionMigration::run(delta).unwrap(); + let document = Document::from_transaction(transaction).unwrap(); + let s = document.get_content(true).unwrap(); + assert!(!s.is_empty()); + } + + const DELTA_STR: &str = r#"[ + { + "insert": "\n👋 Welcome to AppFlowy!" + }, + { + "insert": "\n", + "attributes": { + "header": 1 + } + }, + { + "insert": "\nHere are the basics" + }, + { + "insert": "\n", + "attributes": { + "header": 2 + } + }, + { + "insert": "Click anywhere and just start typing" + }, + { + "insert": "\n", + "attributes": { + "list": "unchecked" + } + }, + { + "insert": "Highlight", + "attributes": { + "background": "$fff2cd" + } + }, + { + "insert": " any text, and use the menu at the bottom to " + }, + { + "insert": "style", + "attributes": { + "italic": true + } + }, + { + "insert": " " + }, + { + "insert": "your", + "attributes": { + "bold": true + } + }, + { + "insert": " " + }, + { + "insert": "writing", + "attributes": { + "underline": true + } + }, + { + "insert": " " + }, + { + "insert": "however", + "attributes": { + "code": true + } + }, + { + "insert": " " + }, + { + "insert": "you", + "attributes": { + "strike": true + } + }, + { + "insert": " " + }, + { + "insert": "like", + "attributes": { + "background": "$e8e0ff" + } + }, + { + "insert": "\n", + "attributes": { + "list": "unchecked" + } + }, + { + "insert": "Click " + }, + { + "insert": "+ New Page", + "attributes": { + "background": "$defff1", + "bold": true + } + }, + { + "insert": " button at the bottom of your sidebar to add a new page" + }, + { + "insert": "\n", + "attributes": { + "list": "unchecked" + } + }, + { + "insert": "Click the " + }, + { + "insert": "'", + "attributes": { + "background": "$defff1" + } + }, + { + "insert": "+", + "attributes": { + "background": "$defff1", + "bold": true + } + }, + { + "insert": "'", + "attributes": { + "background": "$defff1" + } + }, + { + "insert": " next to any page title in the sidebar to quickly add a new subpage" + }, + { + "insert": "\n", + "attributes": { + "list": "unchecked" + } + }, + { + "insert": "\nHave a question? " + }, + { + "insert": "\n", + "attributes": { + "header": 2 + } + }, + { + "insert": "Click the " + }, + { + "insert": "'?'", + "attributes": { + "background": "$defff1", + "bold": true + } + }, + { + "insert": " at the bottom right for help and support.\n\nLike AppFlowy? Follow us:" + }, + { + "insert": "\n", + "attributes": { + "header": 2 + } + }, + { + "insert": "GitHub: https://github.com/AppFlowy-IO/appflowy" + }, + { + "insert": "\n", + "attributes": { + "blockquote": true + } + }, + { + "insert": "Twitter: https://twitter.com/appflowy" + }, + { + "insert": "\n", + "attributes": { + "blockquote": true + } + }, + { + "insert": "Newsletter: https://www.appflowy.io/blog" + }, + { + "insert": "\n", + "attributes": { + "blockquote": true + } + }, + { + "insert": "item 1" + }, + { + "insert": "\n", + "attributes": { + "list": "ordered" + } + }, + { + "insert": "item 2" + }, + { + "insert": "\n", + "attributes": { + "list": "ordered" + } + }, + { + "insert": "item3" + }, + { + "insert": "\n", + "attributes": { + "list": "ordered" + } + }, + { + "insert": "appflowy", + "attributes": { + "link": "https://www.appflowy.io/" + } + } +]"#; +} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs new file mode 100644 index 0000000000..bedd4fea49 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/persistence/mod.rs @@ -0,0 +1,26 @@ +pub mod delta_migration; +pub mod rev_sqlite; + +use crate::services::migration::DocumentMigration; +use crate::DocumentDatabase; +use flowy_error::FlowyResult; +use std::sync::Arc; + +pub struct DocumentPersistence { + pub database: Arc, +} + +impl DocumentPersistence { + pub fn new(database: Arc) -> Self { + Self { database } + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub fn initialize(&self, user_id: &str) -> FlowyResult<()> { + let migration = DocumentMigration::new(user_id, self.database.clone()); + if let Err(e) = migration.run_v1_migration() { + tracing::error!("[Document Migration]: run v1 migration failed: {:?}", e); + } + Ok(()) + } +} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs new file mode 100644 index 0000000000..aa1b670206 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v0.rs @@ -0,0 +1,289 @@ +use bytes::Bytes; +use diesel::{sql_types::Integer, update, SqliteConnection}; +use flowy_database::{ + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{rev_table, rev_table::dsl}, + ConnectionPool, +}; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_http_model::{ + revision::{Revision, RevisionRange}, + util::md5, +}; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; +use std::collections::HashMap; +use std::sync::Arc; + +pub struct SQLiteDeltaDocumentRevisionPersistence { + user_id: String, + pub(crate) pool: Arc, +} + +impl RevisionDiskCache> for SQLiteDeltaDocumentRevisionPersistence { + type Error = FlowyError; + + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let _ = DeltaRevisionSql::create(revision_records, &*conn)?; + Ok(()) + } + + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = DeltaRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; + Ok(records) + } + + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = DeltaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } + + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + let _ = conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + let _ = DeltaRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } + + fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let _ = DeltaRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } + + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + let _ = DeltaRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?; + let _ = DeltaRevisionSql::create(inserted_records, &*conn)?; + Ok(()) + }) + } +} + +impl SQLiteDeltaDocumentRevisionPersistence { + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, + } + } +} + +pub struct DeltaRevisionSql {} + +impl DeltaRevisionSql { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html + + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[TextRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id + ); + let rev_state: TextRevisionState = record.state.into(); + ( + dsl::doc_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + dsl::ty.eq(RevTableType::Local), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::rev_table).values(&records).execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: TextRevisionState = changeset.state.clone().into(); + let filter = dsl::rev_table + .filter(dsl::rev_id.eq(changeset.rev_id.as_ref())) + .filter(dsl::doc_id.eq(changeset.object_id)); + let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; + tracing::debug!( + "[TextRevisionSql] update revision:{} state:to {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::rev_table.filter(dsl::doc_id.eq(object_id)).into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::doc_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::rev_table).into_boxed(); + sql = sql.filter(dsl::doc_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!("[TextRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + + let affected_row = sql.execute(conn)?; + tracing::trace!("[TextRevisionSql] Delete {} rows", affected_row); + Ok(()) + } + + pub fn read_all_documents(user_id: &str, conn: &SqliteConnection) -> Result>, FlowyError> { + let rev_tables = dsl::rev_table.order(dsl::rev_id.asc()).load::(conn)?; + let mut document_map = HashMap::new(); + for rev_table in rev_tables { + document_map + .entry(rev_table.doc_id.clone()) + .or_insert_with(Vec::new) + .push(rev_table); + } + let mut documents = vec![]; + for rev_tables in document_map.into_values() { + let revisions = rev_tables + .into_iter() + .map(|table| { + let record = mk_revision_record_from_table(user_id, table); + record.revision + }) + .collect::>(); + documents.push(revisions); + } + + Ok(documents) + } +} + +#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] +#[table_name = "rev_table"] +struct RevisionTable { + id: i32, + doc_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: TextRevisionState, + ty: RevTableType, // Deprecated +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] +#[repr(i32)] +#[sql_type = "Integer"] +enum TextRevisionState { + Sync = 0, + Ack = 1, +} +impl_sql_integer_expression!(TextRevisionState); +impl_rev_state_map!(TextRevisionState); + +impl std::default::Default for TextRevisionState { + fn default() -> Self { + TextRevisionState::Sync + } +} + +fn mk_revision_record_from_table(_user_id: &str, table: RevisionTable) -> SyncRecord { + let md5 = md5(&table.data); + let revision = Revision::new( + &table.doc_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] +#[repr(i32)] +#[sql_type = "Integer"] +pub enum RevTableType { + Local = 0, + Remote = 1, +} +impl_sql_integer_expression!(RevTableType); + +impl std::default::Default for RevTableType { + fn default() -> Self { + RevTableType::Local + } +} + +impl std::convert::From for RevTableType { + fn from(value: i32) -> Self { + match value { + 0 => RevTableType::Local, + 1 => RevTableType::Remote, + o => { + tracing::error!("Unsupported rev type {}, fallback to RevTableType::Local", o); + RevTableType::Local + } + } + } +} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs new file mode 100644 index 0000000000..3d491055eb --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/document_rev_sqlite_v1.rs @@ -0,0 +1,237 @@ +use bytes::Bytes; +use diesel::{sql_types::Integer, update, SqliteConnection}; +use flowy_database::{ + impl_sql_integer_expression, insert_or_ignore_into, + prelude::*, + schema::{document_rev_table, document_rev_table::dsl}, + ConnectionPool, +}; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_http_model::{ + revision::{Revision, RevisionRange}, + util::md5, +}; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; +use std::sync::Arc; + +pub struct SQLiteDocumentRevisionPersistence { + user_id: String, + pub(crate) pool: Arc, +} + +impl RevisionDiskCache> for SQLiteDocumentRevisionPersistence { + type Error = FlowyError; + + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let _ = DocumentRevisionSql::create(revision_records, &*conn)?; + Ok(()) + } + + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + + fn read_revision_records( + &self, + object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + let records = DocumentRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; + Ok(records) + } + + fn read_revision_records_with_range( + &self, + object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let revisions = DocumentRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + Ok(revisions) + } + + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + let conn = &*self.pool.get().map_err(internal_error)?; + let _ = conn.immediate_transaction::<_, FlowyError, _>(|| { + for changeset in changesets { + let _ = DocumentRevisionSql::update(changeset, conn)?; + } + Ok(()) + })?; + Ok(()) + } + + fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { + let conn = &*self.pool.get().map_err(internal_error)?; + let _ = DocumentRevisionSql::delete(object_id, rev_ids, conn)?; + Ok(()) + } + + fn delete_and_insert_records( + &self, + object_id: &str, + deleted_rev_ids: Option>, + inserted_records: Vec, + ) -> Result<(), Self::Error> { + let conn = self.pool.get().map_err(internal_error)?; + conn.immediate_transaction::<_, FlowyError, _>(|| { + let _ = DocumentRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?; + let _ = DocumentRevisionSql::create(inserted_records, &*conn)?; + Ok(()) + }) + } +} + +impl SQLiteDocumentRevisionPersistence { + pub fn new(user_id: &str, pool: Arc) -> Self { + Self { + user_id: user_id.to_owned(), + pool, + } + } +} + +struct DocumentRevisionSql {} + +impl DocumentRevisionSql { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + // Batch insert: https://diesel.rs/guides/all-about-inserts.html + let records = revision_records + .into_iter() + .map(|record| { + tracing::trace!( + "[DocumentRevisionSql] create revision: {}:{:?}", + record.revision.object_id, + record.revision.rev_id + ); + let rev_state: DocumentRevisionState = record.state.into(); + ( + dsl::document_id.eq(record.revision.object_id), + dsl::base_rev_id.eq(record.revision.base_rev_id), + dsl::rev_id.eq(record.revision.rev_id), + dsl::data.eq(record.revision.bytes), + dsl::state.eq(rev_state), + ) + }) + .collect::>(); + + let _ = insert_or_ignore_into(dsl::document_rev_table) + .values(&records) + .execute(conn)?; + Ok(()) + } + + fn update(changeset: RevisionChangeset, conn: &SqliteConnection) -> Result<(), FlowyError> { + let state: DocumentRevisionState = changeset.state.clone().into(); + let filter = dsl::document_rev_table + .filter(dsl::rev_id.eq(changeset.rev_id.as_ref())) + .filter(dsl::document_id.eq(changeset.object_id)); + let _ = update(filter).set(dsl::state.eq(state)).execute(conn)?; + tracing::debug!( + "[DocumentRevisionSql] update revision:{} state:to {:?}", + changeset.rev_id, + changeset.state + ); + Ok(()) + } + + fn read( + user_id: &str, + object_id: &str, + rev_ids: Option>, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let mut sql = dsl::document_rev_table + .filter(dsl::document_id.eq(object_id)) + .into_boxed(); + if let Some(rev_ids) = rev_ids { + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + let rows = sql.order(dsl::rev_id.asc()).load::(conn)?; + let records = rows + .into_iter() + .map(|row| mk_revision_record_from_table(user_id, row)) + .collect::>(); + + Ok(records) + } + + fn read_with_range( + user_id: &str, + object_id: &str, + range: RevisionRange, + conn: &SqliteConnection, + ) -> Result, FlowyError> { + let rev_tables = dsl::document_rev_table + .filter(dsl::rev_id.ge(range.start)) + .filter(dsl::rev_id.le(range.end)) + .filter(dsl::document_id.eq(object_id)) + .order(dsl::rev_id.asc()) + .load::(conn)?; + + let revisions = rev_tables + .into_iter() + .map(|table| mk_revision_record_from_table(user_id, table)) + .collect::>(); + Ok(revisions) + } + + fn delete(object_id: &str, rev_ids: Option>, conn: &SqliteConnection) -> Result<(), FlowyError> { + let mut sql = diesel::delete(dsl::document_rev_table).into_boxed(); + sql = sql.filter(dsl::document_id.eq(object_id)); + + if let Some(rev_ids) = rev_ids { + tracing::trace!("[DocumentRevisionSql] Delete revision: {}:{:?}", object_id, rev_ids); + sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); + } + + let affected_row = sql.execute(conn)?; + tracing::trace!("[DocumentRevisionSql] Delete {} rows", affected_row); + Ok(()) + } +} + +#[derive(PartialEq, Clone, Debug, Queryable, Identifiable, Insertable, Associations)] +#[table_name = "document_rev_table"] +struct DocumentRevisionTable { + id: i32, + document_id: String, + base_rev_id: i64, + rev_id: i64, + data: Vec, + state: DocumentRevisionState, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] +#[repr(i32)] +#[sql_type = "Integer"] +enum DocumentRevisionState { + Sync = 0, + Ack = 1, +} +impl_sql_integer_expression!(DocumentRevisionState); +impl_rev_state_map!(DocumentRevisionState); + +impl std::default::Default for DocumentRevisionState { + fn default() -> Self { + DocumentRevisionState::Sync + } +} + +fn mk_revision_record_from_table(_user_id: &str, table: DocumentRevisionTable) -> SyncRecord { + let md5 = md5(&table.data); + let revision = Revision::new( + &table.document_id, + table.base_rev_id, + table.rev_id, + Bytes::from(table.data), + md5, + ); + SyncRecord { + revision, + state: table.state.into(), + write_to_disk: false, + } +} diff --git a/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs new file mode 100644 index 0000000000..e0c1920633 --- /dev/null +++ b/frontend/rust-lib/flowy-document/src/services/persistence/rev_sqlite/mod.rs @@ -0,0 +1,5 @@ +mod document_rev_sqlite_v0; +mod document_rev_sqlite_v1; + +pub use document_rev_sqlite_v0::*; +pub use document_rev_sqlite_v1::*; diff --git a/frontend/rust-lib/flowy-document/tests/document/mod.rs b/frontend/rust-lib/flowy-document/tests/document/mod.rs deleted file mode 100644 index 8aa4c774ed..0000000000 --- a/frontend/rust-lib/flowy-document/tests/document/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod script; -mod text_block_test; diff --git a/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs b/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs index 44016fee15..f17f8dad15 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/attribute_test.rs @@ -1,9 +1,9 @@ #![cfg_attr(rustfmt, rustfmt::skip)] use crate::editor::{TestBuilder, TestOp::*}; -use flowy_sync::client_document::{NewlineDoc, EmptyDoc}; +use flowy_sync::client_document::{NewlineDocument, EmptyDocument}; use lib_ot::core::{Interval, OperationTransform, NEW_LINE, WHITESPACE, OTString}; use unicode_segmentation::UnicodeSegmentation; -use lib_ot::text_delta::TextOperations; +use lib_ot::text_delta::DeltaTextOperations; #[test] fn attributes_bold_added() { @@ -19,7 +19,7 @@ fn attributes_bold_added() { ]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -29,9 +29,9 @@ fn attributes_bold_added_and_invert_all() { Bold(0, Interval::new(0, 3), true), AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), Bold(0, Interval::new(0, 3), false), - AssertDocJson(0, r#"[{"insert":"123"}]"#), + AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":false}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -41,9 +41,9 @@ fn attributes_bold_added_and_invert_partial_suffix() { Bold(0, Interval::new(0, 4), true), AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), Bold(0, Interval::new(2, 4), false), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34"}]"#), + AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34","attributes":{"bold":false}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -53,11 +53,11 @@ fn attributes_bold_added_and_invert_partial_suffix2() { Bold(0, Interval::new(0, 4), true), AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), Bold(0, Interval::new(2, 4), false), - AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34"}]"#), + AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34","attributes":{"bold":false}}]"#), Bold(0, Interval::new(2, 4), true), AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -85,7 +85,7 @@ fn attributes_bold_added_with_new_line() { r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\na\n"},{"insert":"456","attributes":{"bold":true}},{"insert":"\n"}]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -95,9 +95,9 @@ fn attributes_bold_added_and_invert_partial_prefix() { Bold(0, Interval::new(0, 4), true), AssertDocJson(0, r#"[{"insert":"1234","attributes":{"bold":true}}]"#), Bold(0, Interval::new(0, 2), false), - AssertDocJson(0, r#"[{"insert":"12"},{"insert":"34","attributes":{"bold":true}}]"#), + AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":false}},{"insert":"34","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -109,7 +109,7 @@ fn attributes_bold_added_consecutive() { Bold(0, Interval::new(1, 2), true), AssertDocJson(0, r#"[{"insert":"12","attributes":{"bold":true}},{"insert":"34"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -128,7 +128,7 @@ fn attributes_bold_added_italic() { r#"[{"insert":"12345678","attributes":{"bold":true,"italic":true}},{"insert":"\n"}]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -156,7 +156,7 @@ fn attributes_bold_added_italic2() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -193,7 +193,7 @@ fn attributes_bold_added_italic3() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -229,7 +229,7 @@ fn attributes_bold_added_italic_delete() { AssertDocJson(0, r#"[{"insert":"67"},{"insert":"89","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -240,7 +240,7 @@ fn attributes_merge_inserted_text_with_same_attribute() { InsertBold(0, "456", Interval::new(3, 6)), AssertDocJson(0, r#"[{"insert":"123456","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -255,7 +255,7 @@ fn attributes_compose_attr_attributes_with_attr_attributes_test() { AssertDocJson(1, r#"[{"insert":"1234567","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -296,7 +296,7 @@ fn attributes_compose_attr_attributes_with_attr_attributes_test2() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -312,7 +312,7 @@ fn attributes_compose_attr_attributes_with_no_attr_attributes_test() { AssertDocJson(0, expected), AssertDocJson(1, expected), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -324,7 +324,7 @@ fn attributes_replace_heading() { AssertDocJson(0, r#"[{"insert":"3456","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -336,7 +336,7 @@ fn attributes_replace_trailing() { AssertDocJson(0, r#"[{"insert":"12345","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -350,7 +350,7 @@ fn attributes_replace_middle() { AssertDocJson(0, r#"[{"insert":"34","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -362,7 +362,7 @@ fn attributes_replace_all() { AssertDocJson(0, r#"[]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -374,7 +374,7 @@ fn attributes_replace_with_text() { AssertDocJson(0, r#"[{"insert":"ab"},{"insert":"456","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -390,7 +390,7 @@ fn attributes_header_insert_newline_at_middle() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -415,7 +415,7 @@ fn attributes_header_insert_double_newline_at_middle() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -430,7 +430,7 @@ fn attributes_header_insert_newline_at_trailing() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -446,7 +446,7 @@ fn attributes_header_insert_double_newline_at_trailing() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -460,7 +460,7 @@ fn attributes_link_added() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -479,7 +479,7 @@ fn attributes_link_format_with_bold() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -498,7 +498,7 @@ fn attributes_link_insert_char_at_head() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -513,7 +513,7 @@ fn attributes_link_insert_char_at_middle() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -532,7 +532,7 @@ fn attributes_link_insert_char_at_trailing() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -547,7 +547,7 @@ fn attributes_link_insert_newline_at_middle() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -563,7 +563,7 @@ fn attributes_link_auto_format() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -579,7 +579,7 @@ fn attributes_link_auto_format_exist() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -595,7 +595,7 @@ fn attributes_link_auto_format_exist2() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -606,7 +606,7 @@ fn attributes_bullet_added() { AssertDocJson(0, r#"[{"insert":"12"},{"insert":"\n","attributes":{"list":"bullet"}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -627,7 +627,7 @@ fn attributes_bullet_added_2() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -644,7 +644,7 @@ fn attributes_bullet_remove_partial() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -660,7 +660,7 @@ fn attributes_bullet_auto_exit() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -700,7 +700,7 @@ fn attributes_preserve_block_when_insert_newline_inside() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -717,7 +717,7 @@ fn attributes_preserve_header_format_on_merge() { AssertDocJson(0, r#"[{"insert":"123456"},{"insert":"\n","attributes":{"header":1}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -737,7 +737,7 @@ fn attributes_format_emoji() { r#"[{"insert":"👋 "},{"insert":"\n","attributes":{"header":1}}]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -757,17 +757,17 @@ fn attributes_preserve_list_format_on_merge() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_compose() { - let mut delta = TextOperations::from_json(r#"[{"insert":"\n"}]"#).unwrap(); + let mut delta = DeltaTextOperations::from_json(r#"[{"insert":"\n"}]"#).unwrap(); let deltas = vec![ - TextOperations::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(), - TextOperations::from_json(r#"[{"insert":"a"}]"#).unwrap(), - TextOperations::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(), - TextOperations::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(), + DeltaTextOperations::from_json(r#"[{"retain":1,"attributes":{"list":"unchecked"}}]"#).unwrap(), + DeltaTextOperations::from_json(r#"[{"insert":"a"}]"#).unwrap(), + DeltaTextOperations::from_json(r#"[{"retain":1},{"insert":"\n","attributes":{"list":"unchecked"}}]"#).unwrap(), + DeltaTextOperations::from_json(r#"[{"retain":2},{"retain":1,"attributes":{"list":""}}]"#).unwrap(), ]; for d in deltas { @@ -796,5 +796,5 @@ fn delta_compose() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } diff --git a/frontend/rust-lib/flowy-document/tests/editor/mod.rs b/frontend/rust-lib/flowy-document/tests/editor/mod.rs index d750b459ca..f33cfb8057 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/mod.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/mod.rs @@ -8,7 +8,7 @@ use derive_more::Display; use flowy_sync::client_document::{ClientDocument, InitialDocument}; use lib_ot::{ core::*, - text_delta::{BuildInTextAttribute, TextOperations}, + text_delta::{BuildInTextAttribute, DeltaTextOperations}, }; use rand::{prelude::*, Rng as WrappedRng}; use std::{sync::Once, time::Duration}; @@ -81,8 +81,8 @@ pub enum TestOp { pub struct TestBuilder { documents: Vec, - deltas: Vec>, - primes: Vec>, + deltas: Vec>, + primes: Vec>, } impl TestBuilder { @@ -226,20 +226,20 @@ impl TestBuilder { TestOp::AssertDocJson(delta_i, expected) => { let delta_json = self.documents[*delta_i].get_operations_json(); - let expected_delta: TextOperations = serde_json::from_str(expected).unwrap(); - let target_delta: TextOperations = serde_json::from_str(&delta_json).unwrap(); + let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); + let target_delta: DeltaTextOperations = serde_json::from_str(&delta_json).unwrap(); if expected_delta != target_delta { - log::error!("✅ expect: {}", expected,); - log::error!("❌ receive: {}", delta_json); + println!("✅ expect: {}", expected,); + println!("❌ receive: {}", delta_json); } assert_eq!(target_delta, expected_delta); } TestOp::AssertPrimeJson(doc_i, expected) => { let prime_json = self.primes[*doc_i].as_ref().unwrap().json_str(); - let expected_prime: TextOperations = serde_json::from_str(expected).unwrap(); - let target_prime: TextOperations = serde_json::from_str(&prime_json).unwrap(); + let expected_prime: DeltaTextOperations = serde_json::from_str(expected).unwrap(); + let target_prime: DeltaTextOperations = serde_json::from_str(&prime_json).unwrap(); if expected_prime != target_prime { log::error!("✅ expect prime: {}", expected,); @@ -297,8 +297,8 @@ impl Rng { .collect() } - pub fn gen_delta(&mut self, s: &str) -> TextOperations { - let mut delta = TextOperations::default(); + pub fn gen_delta(&mut self, s: &str) -> DeltaTextOperations { + let mut delta = DeltaTextOperations::default(); let s = OTString::from(s); loop { let left = s.utf16_len() - delta.utf16_base_len; diff --git a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs b/frontend/rust-lib/flowy-document/tests/editor/op_test.rs index df1320ca51..d1cec0c7f0 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/op_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/op_test.rs @@ -1,8 +1,8 @@ #![allow(clippy::all)] use crate::editor::{Rng, TestBuilder, TestOp::*}; -use flowy_sync::client_document::{EmptyDoc, NewlineDoc}; -use lib_ot::text_delta::TextOperationBuilder; -use lib_ot::{core::Interval, core::*, text_delta::TextOperations}; +use flowy_sync::client_document::{EmptyDocument, NewlineDocument}; +use lib_ot::text_delta::DeltaTextOperationBuilder; +use lib_ot::{core::Interval, core::*, text_delta::DeltaTextOperations}; #[test] fn attributes_insert_text() { @@ -11,7 +11,7 @@ fn attributes_insert_text() { Insert(0, "456", 3), AssertDocJson(0, r#"[{"insert":"123456"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -21,7 +21,7 @@ fn attributes_insert_text_at_head() { Insert(0, "456", 0), AssertDocJson(0, r#"[{"insert":"456123"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -31,13 +31,12 @@ fn attributes_insert_text_at_middle() { Insert(0, "456", 1), AssertDocJson(0, r#"[{"insert":"145623"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_get_ops_in_interval_1() { - let operations = OperationsBuilder::new().insert("123").insert("4").build(); - let delta = TextOperationBuilder::from_operations(operations); + let delta = DeltaTextOperationBuilder::new().insert("123").insert("4").build(); let mut iterator = OperationIterator::from_interval(&delta, Interval::new(0, 4)); assert_eq!(iterator.ops(), delta.ops); @@ -45,7 +44,7 @@ fn delta_get_ops_in_interval_1() { #[test] fn delta_get_ops_in_interval_2() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("123"); let insert_b = DeltaOperation::insert("4"); let insert_c = DeltaOperation::insert("5"); @@ -89,7 +88,7 @@ fn delta_get_ops_in_interval_2() { #[test] fn delta_get_ops_in_interval_3() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("123456"); delta.add(insert_a.clone()); assert_eq!( @@ -100,7 +99,7 @@ fn delta_get_ops_in_interval_3() { #[test] fn delta_get_ops_in_interval_4() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("12"); let insert_b = DeltaOperation::insert("34"); let insert_c = DeltaOperation::insert("56"); @@ -130,7 +129,7 @@ fn delta_get_ops_in_interval_4() { #[test] fn delta_get_ops_in_interval_5() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("123456"); let insert_b = DeltaOperation::insert("789"); delta.ops.push(insert_a.clone()); @@ -148,7 +147,7 @@ fn delta_get_ops_in_interval_5() { #[test] fn delta_get_ops_in_interval_6() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("12345678"); delta.add(insert_a.clone()); assert_eq!( @@ -159,7 +158,7 @@ fn delta_get_ops_in_interval_6() { #[test] fn delta_get_ops_in_interval_7() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("12345"); let retain_a = DeltaOperation::retain(3); @@ -179,7 +178,7 @@ fn delta_get_ops_in_interval_7() { #[test] fn delta_op_seek() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let insert_a = DeltaOperation::insert("12345"); let retain_a = DeltaOperation::retain(3); delta.add(insert_a.clone()); @@ -191,7 +190,7 @@ fn delta_op_seek() { #[test] fn delta_utf16_code_unit_seek() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); let mut iter = OperationIterator::new(&delta); @@ -201,7 +200,7 @@ fn delta_utf16_code_unit_seek() { #[test] fn delta_utf16_code_unit_seek_with_attributes() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); let attributes = AttributeBuilder::new() .insert("bold", true) .insert("italic", true) @@ -221,7 +220,7 @@ fn delta_utf16_code_unit_seek_with_attributes() { #[test] fn delta_next_op_len() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); let mut iter = OperationIterator::new(&delta); assert_eq!(iter.next_op_with_len(2).unwrap(), DeltaOperation::insert("12")); @@ -232,7 +231,7 @@ fn delta_next_op_len() { #[test] fn delta_next_op_len_with_chinese() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("你好")); let mut iter = OperationIterator::new(&delta); @@ -242,7 +241,7 @@ fn delta_next_op_len_with_chinese() { #[test] fn delta_next_op_len_with_english() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("ab")); let mut iter = OperationIterator::new(&delta); assert_eq!(iter.next_op_len().unwrap(), 2); @@ -251,7 +250,7 @@ fn delta_next_op_len_with_english() { #[test] fn delta_next_op_len_after_seek() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); let mut iter = OperationIterator::new(&delta); assert_eq!(iter.next_op_len().unwrap(), 5); @@ -264,7 +263,7 @@ fn delta_next_op_len_after_seek() { #[test] fn delta_next_op_len_none() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); let mut iter = OperationIterator::new(&delta); @@ -275,7 +274,7 @@ fn delta_next_op_len_none() { #[test] fn delta_next_op_with_len_zero() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); let mut iter = OperationIterator::new(&delta); assert_eq!(iter.next_op_with_len(0), None,); @@ -284,7 +283,7 @@ fn delta_next_op_with_len_zero() { #[test] fn delta_next_op_with_len_cross_op_return_last() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("12345")); delta.add(DeltaOperation::retain(1)); delta.add(DeltaOperation::insert("678")); @@ -297,7 +296,7 @@ fn delta_next_op_with_len_cross_op_return_last() { #[test] fn lengths() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); assert_eq!(delta.utf16_base_len, 0); assert_eq!(delta.utf16_target_len, 0); delta.retain(5, AttributeHashMap::default()); @@ -315,7 +314,7 @@ fn lengths() { } #[test] fn sequence() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.retain(5, AttributeHashMap::default()); delta.retain(0, AttributeHashMap::default()); delta.insert("appflowy", AttributeHashMap::default()); @@ -348,7 +347,7 @@ fn apply_test() { #[test] fn base_len_test() { - let mut delta_a = TextOperations::default(); + let mut delta_a = DeltaTextOperations::default(); delta_a.insert("a", AttributeHashMap::default()); delta_a.insert("b", AttributeHashMap::default()); delta_a.insert("c", AttributeHashMap::default()); @@ -387,7 +386,7 @@ fn invert_test() { #[test] fn empty_ops() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.retain(0, AttributeHashMap::default()); delta.insert("", AttributeHashMap::default()); delta.delete(0); @@ -395,12 +394,12 @@ fn empty_ops() { } #[test] fn eq() { - let mut delta_a = TextOperations::default(); + let mut delta_a = DeltaTextOperations::default(); delta_a.delete(1); delta_a.insert("lo", AttributeHashMap::default()); delta_a.retain(2, AttributeHashMap::default()); delta_a.retain(3, AttributeHashMap::default()); - let mut delta_b = TextOperations::default(); + let mut delta_b = DeltaTextOperations::default(); delta_b.delete(1); delta_b.insert("l", AttributeHashMap::default()); delta_b.insert("o", AttributeHashMap::default()); @@ -412,7 +411,7 @@ fn eq() { } #[test] fn ops_merging() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); assert_eq!(delta.ops.len(), 0); delta.retain(2, AttributeHashMap::default()); assert_eq!(delta.ops.len(), 1); @@ -436,7 +435,7 @@ fn ops_merging() { #[test] fn is_noop() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); assert!(delta.is_noop()); delta.retain(5, AttributeHashMap::default()); assert!(delta.is_noop()); @@ -484,13 +483,13 @@ fn transform_random_delta() { #[test] fn transform_with_two_delta() { - let mut a = TextOperations::default(); + let mut a = DeltaTextOperations::default(); let mut a_s = String::new(); a.insert("123", AttributeBuilder::new().insert("bold", true).build()); a_s = a.apply(&a_s).unwrap(); assert_eq!(&a_s, "123"); - let mut b = TextOperations::default(); + let mut b = DeltaTextOperations::default(); let mut b_s = String::new(); b.insert("456", AttributeHashMap::default()); b_s = b.apply(&b_s).unwrap(); @@ -528,7 +527,7 @@ fn transform_two_plain_delta() { AssertDocJson(0, r#"[{"insert":"123456"}]"#), AssertDocJson(1, r#"[{"insert":"123456"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -542,7 +541,7 @@ fn transform_two_plain_delta2() { AssertDocJson(0, r#"[{"insert":"123456"}]"#), AssertDocJson(1, r#"[{"insert":"123456"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -560,7 +559,7 @@ fn transform_two_non_seq_delta() { AssertDocJson(0, r#"[{"insert":"123456"}]"#), AssertDocJson(1, r#"[{"insert":"123456789"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -575,15 +574,15 @@ fn transform_two_conflict_non_seq_delta() { AssertDocJson(0, r#"[{"insert":"123456"}]"#), AssertDocJson(1, r#"[{"insert":"12378456"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] fn delta_invert_no_attribute_delta() { - let mut delta = TextOperations::default(); + let mut delta = DeltaTextOperations::default(); delta.add(DeltaOperation::insert("123")); - let mut change = TextOperations::default(); + let mut change = DeltaTextOperations::default(); change.add(DeltaOperation::retain(3)); change.add(DeltaOperation::insert("456")); let undo = change.invert(&delta); @@ -602,7 +601,7 @@ fn delta_invert_no_attribute_delta2() { Invert(0, 1), AssertDocJson(0, r#"[{"insert":"123"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -615,7 +614,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta() { Invert(0, 1), AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -650,7 +649,7 @@ fn delta_invert_attribute_delta_with_no_attribute_delta2() { ]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -663,7 +662,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta() { Invert(0, 1), AssertDocJson(0, r#"[{"insert":"123"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -682,7 +681,7 @@ fn delta_invert_no_attribute_delta_with_attribute_delta2() { Invert(0, 1), AssertDocJson(0, r#"[{"insert":"123"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -723,7 +722,7 @@ fn delta_invert_attribute_delta_with_attribute_delta() { ]"#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -733,7 +732,7 @@ fn delta_compose_str() { Insert(0, "2", 1), AssertDocJson(0, r#"[{"insert":"12\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -746,5 +745,5 @@ fn delta_compose_with_missing_delta() { AssertDocJson(0, r#"[{"insert":"1234\n"}]"#), AssertStr(1, r#"4\n"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } diff --git a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs b/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs index 1553530668..9941424dc7 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/serde_test.rs @@ -1,8 +1,8 @@ -use flowy_sync::client_document::{ClientDocument, EmptyDoc}; -use lib_ot::text_delta::TextOperation; +use flowy_sync::client_document::{ClientDocument, EmptyDocument}; +use lib_ot::text_delta::DeltaTextOperation; use lib_ot::{ core::*, - text_delta::{BuildInTextAttribute, TextOperations}, + text_delta::{BuildInTextAttribute, DeltaTextOperations}, }; #[test] @@ -15,7 +15,7 @@ fn operation_insert_serialize_test() { let json = serde_json::to_string(&operation).unwrap(); eprintln!("{}", json); - let insert_op: TextOperation = serde_json::from_str(&json).unwrap(); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); assert_eq!(insert_op, operation); } @@ -24,15 +24,15 @@ fn operation_retain_serialize_test() { let operation = DeltaOperation::Retain(12.into()); let json = serde_json::to_string(&operation).unwrap(); eprintln!("{}", json); - let insert_op: TextOperation = serde_json::from_str(&json).unwrap(); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); assert_eq!(insert_op, operation); } #[test] fn operation_delete_serialize_test() { - let operation = TextOperation::Delete(2); + let operation = DeltaTextOperation::Delete(2); let json = serde_json::to_string(&operation).unwrap(); - let insert_op: TextOperation = serde_json::from_str(&json).unwrap(); + let insert_op: DeltaTextOperation = serde_json::from_str(&json).unwrap(); assert_eq!(insert_op, operation); } @@ -77,7 +77,7 @@ fn delta_deserialize_test() { {"retain":2,"attributes":{"italic":true,"bold":true}}, {"retain":2,"attributes":{"italic":true,"bold":true}} ]"#; - let delta = TextOperations::from_json(json).unwrap(); + let delta = DeltaTextOperations::from_json(json).unwrap(); eprintln!("{}", delta); } @@ -86,12 +86,12 @@ fn delta_deserialize_null_test() { let json = r#"[ {"retain":7,"attributes":{"bold":null}} ]"#; - let delta1 = TextOperations::from_json(json).unwrap(); + let delta1 = DeltaTextOperations::from_json(json).unwrap(); let mut attribute = BuildInTextAttribute::Bold(true); - attribute.remove_value(); + attribute.clear(); - let delta2 = OperationBuilder::new() + let delta2 = DeltaOperationBuilder::new() .retain_with_attributes(7, attribute.into()) .build(); @@ -101,7 +101,7 @@ fn delta_deserialize_null_test() { #[test] fn document_insert_serde_test() { - let mut document = ClientDocument::new::(); + let mut document = ClientDocument::new::(); document.insert(0, "\n").unwrap(); document.insert(0, "123").unwrap(); let json = document.get_operations_json(); diff --git a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs b/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs index 4810de202a..94be66f1b9 100644 --- a/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs +++ b/frontend/rust-lib/flowy-document/tests/editor/undo_redo_test.rs @@ -1,11 +1,11 @@ use crate::editor::{TestBuilder, TestOp::*}; -use flowy_sync::client_document::{EmptyDoc, NewlineDoc, RECORD_THRESHOLD}; +use flowy_sync::client_document::{EmptyDocument, NewlineDocument, RECORD_THRESHOLD}; use lib_ot::core::{Interval, NEW_LINE, WHITESPACE}; #[test] fn history_insert_undo() { let ops = vec![Insert(0, "123", 0), Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#)]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -19,7 +19,7 @@ fn history_insert_undo_with_lagging() { Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -32,7 +32,7 @@ fn history_insert_redo() { Redo(0), AssertDocJson(0, r#"[{"insert":"123\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -51,7 +51,7 @@ fn history_insert_redo_with_lagging() { Undo(0), AssertDocJson(0, r#"[{"insert":"123\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -62,7 +62,7 @@ fn history_bold_undo() { Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -74,7 +74,7 @@ fn history_bold_undo_with_lagging() { Undo(0), AssertDocJson(0, r#"[{"insert":"123\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -87,7 +87,7 @@ fn history_bold_redo() { Redo(0), AssertDocJson(0, r#" [{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -101,7 +101,7 @@ fn history_bold_redo_with_lagging() { Redo(0), AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -115,7 +115,7 @@ fn history_delete_undo() { Undo(0), AssertDocJson(0, r#"[{"insert":"123"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -134,7 +134,7 @@ fn history_delete_undo_2() { Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -161,7 +161,7 @@ fn history_delete_undo_with_lagging() { "#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -175,7 +175,7 @@ fn history_delete_redo() { Redo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -194,7 +194,7 @@ fn history_replace_undo() { Undo(0), AssertDocJson(0, r#"[{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -215,7 +215,7 @@ fn history_replace_undo_with_lagging() { Undo(0), AssertDocJson(0, r#"[{"insert":"123","attributes":{"bold":true}},{"insert":"\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -234,7 +234,7 @@ fn history_replace_redo() { "#, ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -253,7 +253,7 @@ fn history_header_added_undo() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -272,7 +272,7 @@ fn history_link_added_undo() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -291,7 +291,7 @@ fn history_link_auto_format_undo_with_lagging() { AssertDocJson(0, r#"[{"insert":"https://appflowy.io\n"}]"#), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -314,7 +314,7 @@ fn history_bullet_undo() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -342,7 +342,7 @@ fn history_bullet_undo_with_lagging() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } #[test] @@ -369,5 +369,5 @@ fn history_undo_attribute_on_merge_between_line() { ), ]; - TestBuilder::new().run_scripts::(ops); + TestBuilder::new().run_scripts::(ops); } diff --git a/frontend/rust-lib/flowy-document/tests/main.rs b/frontend/rust-lib/flowy-document/tests/main.rs index c271556ccd..49f15af608 100644 --- a/frontend/rust-lib/flowy-document/tests/main.rs +++ b/frontend/rust-lib/flowy-document/tests/main.rs @@ -1,2 +1,3 @@ -mod document; mod editor; +mod new_document; +mod old_document; diff --git a/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs b/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs new file mode 100644 index 0000000000..4480623fbf --- /dev/null +++ b/frontend/rust-lib/flowy-document/tests/new_document/document_compose_test.rs @@ -0,0 +1,24 @@ +use crate::new_document::script::DocumentEditorTest; +use crate::new_document::script::EditScript::*; + +#[tokio::test] +async fn document_insert_h1_style_test() { + let scripts = vec![ + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"insert":"/"}],"inverted":[{"delete":1}]}],"after_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"/"}]}]}}"#, + }, + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update_text","path":[0,0],"delta":[{"delete":1}],"inverted":[{"insert":"/"}]}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":1},"end":{"path":[0,0],"offset":1}}}"#, + }, + ComposeTransactionStr { + transaction: r#"{"operations":[{"op":"update","path":[0,0],"attributes":{"subtype":"heading","heading":"h1"},"oldAttributes":{"subtype":null,"heading":null}}],"after_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}},"before_selection":{"start":{"path":[0,0],"offset":0},"end":{"path":[0,0],"offset":0}}}"#, + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","attributes":{"subtype":"heading","heading":"h1"}}]}}"#, + }, + ]; + DocumentEditorTest::new().await.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-document/tests/new_document/mod.rs b/frontend/rust-lib/flowy-document/tests/new_document/mod.rs new file mode 100644 index 0000000000..f4464c9147 --- /dev/null +++ b/frontend/rust-lib/flowy-document/tests/new_document/mod.rs @@ -0,0 +1,3 @@ +mod document_compose_test; +mod script; +mod test; diff --git a/frontend/rust-lib/flowy-document/tests/new_document/script.rs b/frontend/rust-lib/flowy-document/tests/new_document/script.rs new file mode 100644 index 0000000000..90a48e1e5e --- /dev/null +++ b/frontend/rust-lib/flowy-document/tests/new_document/script.rs @@ -0,0 +1,116 @@ +use flowy_document::editor::{AppFlowyDocumentEditor, Document, DocumentTransaction}; + +use flowy_document::entities::DocumentVersionPB; +use flowy_test::helper::ViewTest; +use flowy_test::FlowySDKTest; +use lib_ot::core::{Changeset, NodeDataBuilder, NodeOperation, Path, Transaction}; +use lib_ot::text_delta::DeltaTextOperations; +use std::sync::Arc; + +pub enum EditScript { + InsertText { + path: Path, + delta: DeltaTextOperations, + }, + UpdateText { + path: Path, + delta: DeltaTextOperations, + }, + #[allow(dead_code)] + ComposeTransaction { + transaction: Transaction, + }, + ComposeTransactionStr { + transaction: &'static str, + }, + Delete { + path: Path, + }, + AssertContent { + expected: &'static str, + }, + AssertPrettyContent { + expected: &'static str, + }, +} + +pub struct DocumentEditorTest { + pub sdk: FlowySDKTest, + pub editor: Arc, +} + +impl DocumentEditorTest { + pub async fn new() -> Self { + let version = DocumentVersionPB::V1; + let sdk = FlowySDKTest::new(version.clone()); + let _ = sdk.init_user().await; + + let test = ViewTest::new_document_view(&sdk).await; + let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap(); + let editor = match document_editor.as_any().downcast_ref::>() { + None => panic!(), + Some(editor) => editor.clone(), + }; + + Self { sdk, editor } + } + + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + async fn run_script(&self, script: EditScript) { + match script { + EditScript::InsertText { path, delta } => { + let node_data = NodeDataBuilder::new("text").insert_delta(delta).build(); + let operation = NodeOperation::Insert { + path, + nodes: vec![node_data], + }; + self.editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + } + EditScript::UpdateText { path, delta } => { + let inverted = delta.invert_str(""); + let changeset = Changeset::Delta { delta, inverted }; + let operation = NodeOperation::Update { path, changeset }; + self.editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + } + EditScript::ComposeTransaction { transaction } => { + self.editor.apply_transaction(transaction).await.unwrap(); + } + EditScript::ComposeTransactionStr { transaction } => { + let document_transaction = serde_json::from_str::(transaction).unwrap(); + let transaction: Transaction = document_transaction.into(); + self.editor.apply_transaction(transaction).await.unwrap(); + } + EditScript::Delete { path } => { + let operation = NodeOperation::Delete { path, nodes: vec![] }; + self.editor + .apply_transaction(Transaction::from_operations(vec![operation])) + .await + .unwrap(); + } + EditScript::AssertContent { expected } => { + // + let content = self.editor.get_content(false).await.unwrap(); + let expected_document: Document = serde_json::from_str(expected).unwrap(); + let expected = serde_json::to_string(&expected_document).unwrap(); + + assert_eq!(content, expected); + } + EditScript::AssertPrettyContent { expected } => { + // + let content = self.editor.get_content(true).await.unwrap(); + assert_eq!(content, expected); + } + } + } +} diff --git a/frontend/rust-lib/flowy-document/tests/new_document/test.rs b/frontend/rust-lib/flowy-document/tests/new_document/test.rs new file mode 100644 index 0000000000..c54c676a8f --- /dev/null +++ b/frontend/rust-lib/flowy-document/tests/new_document/test.rs @@ -0,0 +1,156 @@ +use crate::new_document::script::DocumentEditorTest; +use crate::new_document::script::EditScript::*; + +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[tokio::test] +async fn document_initialize_test() { + let scripts = vec![AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text"}]}}"#, + }]; + DocumentEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn document_insert_text_test() { + let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); + let expected = r#"{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "delta": [ + { + "insert": "Hello world" + } + ] + }, + { + "type": "text" + } + ] + } +}"#; + let scripts = vec![ + InsertText { + path: vec![0, 0].into(), + delta, + }, + AssertPrettyContent { expected }, + ]; + DocumentEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn document_update_text_test() { + let test = DocumentEditorTest::new().await; + let hello_world = "Hello world".to_string(); + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new().insert(&hello_world).build(), + }, + AssertPrettyContent { + expected: r#"{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "delta": [ + { + "insert": "Hello world" + } + ] + } + ] + } +}"#, + }, + ]; + + test.run_scripts(scripts).await; + + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new() + .retain(hello_world.len()) + .insert(", AppFlowy") + .build(), + }, + AssertPrettyContent { + expected: r#"{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "delta": [ + { + "insert": "Hello world, AppFlowy" + } + ] + } + ] + } +}"#, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn document_delete_text_test() { + let expected = r#"{ + "document": { + "type": "editor", + "children": [ + { + "type": "text", + "delta": [ + { + "insert": "Hello" + } + ] + } + ] + } +}"#; + let hello_world = "Hello world".to_string(); + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new().insert(&hello_world).build(), + }, + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new().retain(5).delete(6).build(), + }, + AssertPrettyContent { expected }, + ]; + + DocumentEditorTest::new().await.run_scripts(scripts).await; +} + +#[tokio::test] +async fn document_delete_node_test() { + let scripts = vec![ + UpdateText { + path: vec![0, 0].into(), + delta: DeltaTextOperationBuilder::new().insert("Hello world").build(), + }, + AssertContent { + expected: r#"{"document":{"type":"editor","children":[{"type":"text","delta":[{"insert":"Hello world"}]}]}}"#, + }, + Delete { + path: vec![0, 0].into(), + }, + AssertContent { + expected: r#"{"document":{"type":"editor"}}"#, + }, + ]; + + DocumentEditorTest::new().await.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-document/tests/old_document/mod.rs b/frontend/rust-lib/flowy-document/tests/old_document/mod.rs new file mode 100644 index 0000000000..395d1bfe2f --- /dev/null +++ b/frontend/rust-lib/flowy-document/tests/old_document/mod.rs @@ -0,0 +1,2 @@ +mod old_document_test; +mod script; diff --git a/frontend/rust-lib/flowy-document/tests/document/text_block_test.rs b/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs similarity index 80% rename from frontend/rust-lib/flowy-document/tests/document/text_block_test.rs rename to frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs index 436411f80b..0c35fa1ac3 100644 --- a/frontend/rust-lib/flowy-document/tests/document/text_block_test.rs +++ b/frontend/rust-lib/flowy-document/tests/old_document/old_document_test.rs @@ -1,4 +1,4 @@ -use crate::document::script::{EditorScript::*, *}; +use crate::old_document::script::{EditorScript::*, *}; use flowy_revision::disk::RevisionState; use lib_ot::core::{count_utf16_code_units, Interval}; @@ -14,7 +14,7 @@ async fn text_block_sync_current_rev_id_check() { AssertNextSyncRevId(None), AssertJson(r#"[{"insert":"123\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -28,7 +28,7 @@ async fn text_block_sync_state_check() { AssertRevisionState(3, RevisionState::Ack), AssertJson(r#"[{"insert":"123\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -40,7 +40,7 @@ async fn text_block_sync_insert_test() { AssertJson(r#"[{"insert":"123\n"}]"#), AssertNextSyncRevId(None), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -52,7 +52,7 @@ async fn text_block_sync_insert_in_chinese() { InsertText("好", offset), AssertJson(r#"[{"insert":"你好\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -64,7 +64,7 @@ async fn text_block_sync_insert_with_emoji() { InsertText("☺️", offset), AssertJson(r#"[{"insert":"😁☺️\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -76,7 +76,7 @@ async fn text_block_sync_delete_in_english() { Delete(Interval::new(0, 2)), AssertJson(r#"[{"insert":"3\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -89,7 +89,7 @@ async fn text_block_sync_delete_in_chinese() { Delete(Interval::new(0, offset)), AssertJson(r#"[{"insert":"好\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } #[tokio::test] @@ -101,5 +101,5 @@ async fn text_block_sync_replace_test() { Replace(Interval::new(0, 3), "abc"), AssertJson(r#"[{"insert":"abc\n"}]"#), ]; - DocumentEditorTest::new().await.run_scripts(scripts).await; + DeltaDocumentEditorTest::new().await.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-document/tests/document/script.rs b/frontend/rust-lib/flowy-document/tests/old_document/script.rs similarity index 80% rename from frontend/rust-lib/flowy-document/tests/document/script.rs rename to frontend/rust-lib/flowy-document/tests/old_document/script.rs index 3055e7224d..cd8e8f79f4 100644 --- a/frontend/rust-lib/flowy-document/tests/document/script.rs +++ b/frontend/rust-lib/flowy-document/tests/old_document/script.rs @@ -1,8 +1,8 @@ -use flowy_document::editor::DocumentEditor; +use flowy_document::old_editor::editor::DeltaDocumentEditor; use flowy_document::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS; use flowy_revision::disk::RevisionState; use flowy_test::{helper::ViewTest, FlowySDKTest}; -use lib_ot::{core::Interval, text_delta::TextOperations}; +use lib_ot::{core::Interval, text_delta::DeltaTextOperations}; use std::sync::Arc; use tokio::time::{sleep, Duration}; @@ -17,21 +17,21 @@ pub enum EditorScript { AssertJson(&'static str), } -pub struct DocumentEditorTest { +pub struct DeltaDocumentEditorTest { pub sdk: FlowySDKTest, - pub editor: Arc, + pub editor: Arc, } -impl DocumentEditorTest { +impl DeltaDocumentEditorTest { pub async fn new() -> Self { let sdk = FlowySDKTest::default(); let _ = sdk.init_user().await; - let test = ViewTest::new_text_block_view(&sdk).await; - let editor = sdk - .text_block_manager - .open_document_editor(&test.view.id) - .await - .unwrap(); + let test = ViewTest::new_document_view(&sdk).await; + let document_editor = sdk.document_manager.open_document_editor(&test.view.id).await.unwrap(); + let editor = match document_editor.as_any().downcast_ref::>() { + None => panic!(), + Some(editor) => editor.clone(), + }; Self { sdk, editor } } @@ -75,7 +75,7 @@ impl DocumentEditorTest { assert_eq!(next_revision.rev_id, rev_id.unwrap()); } EditorScript::AssertJson(expected) => { - let expected_delta: TextOperations = serde_json::from_str(expected).unwrap(); + let expected_delta: DeltaTextOperations = serde_json::from_str(expected).unwrap(); let delta = self.editor.document_operations().await.unwrap(); if expected_delta != delta { eprintln!("✅ expect: {}", expected,); diff --git a/frontend/rust-lib/flowy-error/Cargo.toml b/frontend/rust-lib/flowy-error/Cargo.toml index 5041facaca..09e3e4eb70 100644 --- a/frontend/rust-lib/flowy-error/Cargo.toml +++ b/frontend/rust-lib/flowy-error/Cargo.toml @@ -30,4 +30,4 @@ db = ["flowy-database", "lib-sqlite", "r2d2"] dart = ["flowy-error-code/dart", "lib-infra/dart"] [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen"] } \ No newline at end of file +lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] } \ No newline at end of file diff --git a/frontend/rust-lib/flowy-error/src/errors.rs b/frontend/rust-lib/flowy-error/src/errors.rs index bc453054aa..8b32005378 100644 --- a/frontend/rust-lib/flowy-error/src/errors.rs +++ b/frontend/rust-lib/flowy-error/src/errors.rs @@ -67,6 +67,7 @@ impl FlowyError { static_flowy_error!(text_too_long, ErrorCode::TextTooLong); static_flowy_error!(invalid_data, ErrorCode::InvalidData); static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds); + static_flowy_error!(serde, ErrorCode::Serde); } impl std::convert::From for FlowyError { @@ -111,3 +112,5 @@ impl std::convert::From for FlowyError { FlowyError::internal().context(e) } } + +impl std::error::Error for FlowyError {} diff --git a/frontend/rust-lib/flowy-folder/Cargo.toml b/frontend/rust-lib/flowy-folder/Cargo.toml index 70e37a3459..adde645085 100644 --- a/frontend/rust-lib/flowy-folder/Cargo.toml +++ b/frontend/rust-lib/flowy-folder/Cargo.toml @@ -6,8 +6,9 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flowy-folder-data-model = { path = "../../../shared-lib/flowy-folder-data-model" } +folder-rev-model = { path = "../../../shared-lib/folder-rev-model" } flowy-sync = { path = "../../../shared-lib/flowy-sync" } +flowy-http-model = { path = "../../../shared-lib/flowy-http-model" } flowy-derive = { path = "../../../shared-lib/flowy-derive" } lib-ot = { path = "../../../shared-lib/lib-ot" } lib-infra = { path = "../../../shared-lib/lib-infra" } @@ -19,7 +20,7 @@ dart-notify = { path = "../dart-notify" } lib-dispatch = { path = "../lib-dispatch" } flowy-revision = { path = "../flowy-revision" } -parking_lot = "0.11" +parking_lot = "0.12.1" protobuf = {version = "2.18.0"} log = "0.4.14" diesel = {version = "1.4.8", features = ["sqlite"]} @@ -41,7 +42,7 @@ flowy-folder = { path = "../flowy-folder", features = ["flowy_unit_test"]} flowy-test = { path = "../flowy-test" } [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] } +lib-infra = { path = "../../../shared-lib/lib-infra", features = [ "proto_gen"] } [features] default = [] diff --git a/frontend/rust-lib/flowy-folder/src/dart_notification.rs b/frontend/rust-lib/flowy-folder/src/dart_notification.rs index 2152abce48..efbdf113ea 100644 --- a/frontend/rust-lib/flowy-folder/src/dart_notification.rs +++ b/frontend/rust-lib/flowy-folder/src/dart_notification.rs @@ -12,7 +12,6 @@ pub(crate) enum FolderNotification { WorkspaceAppsChanged = 14, WorkspaceSetting = 15, AppUpdated = 21, - AppViewsChanged = 24, ViewUpdated = 31, ViewDeleted = 32, ViewRestored = 33, diff --git a/frontend/rust-lib/flowy-folder/src/entities/app.rs b/frontend/rust-lib/flowy-folder/src/entities/app.rs index 3a5dc3099e..9b4e0ae7ee 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/app.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/app.rs @@ -8,7 +8,7 @@ use crate::{ impl_def_and_def_mut, }; use flowy_derive::ProtoBuf; -use flowy_folder_data_model::revision::AppRevision; +use folder_rev_model::AppRevision; use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] diff --git a/frontend/rust-lib/flowy-folder/src/entities/trash.rs b/frontend/rust-lib/flowy-folder/src/entities/trash.rs index 15358ba3e5..70143ca17b 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/trash.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/trash.rs @@ -1,6 +1,6 @@ use crate::impl_def_and_def_mut; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_folder_data_model::revision::{TrashRevision, TrashTypeRevision}; +use folder_rev_model::{TrashRevision, TrashTypeRevision}; use serde::{Deserialize, Serialize}; use std::fmt::Formatter; diff --git a/frontend/rust-lib/flowy-folder/src/entities/view.rs b/frontend/rust-lib/flowy-folder/src/entities/view.rs index 61e63c53cb..1b7ff0fe52 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view.rs @@ -7,7 +7,7 @@ use crate::{ impl_def_and_def_mut, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_folder_data_model::revision::{gen_view_id, ViewDataTypeRevision, ViewLayoutTypeRevision, ViewRevision}; +use folder_rev_model::{gen_view_id, ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] @@ -22,7 +22,7 @@ pub struct ViewPB { pub name: String, #[pb(index = 4)] - pub data_type: ViewDataTypePB, + pub data_format: ViewDataFormatPB, #[pb(index = 5)] pub modified_time: i64, @@ -40,7 +40,7 @@ impl std::convert::From for ViewPB { id: rev.id, app_id: rev.app_id, name: rev.name, - data_type: rev.data_type.into(), + data_format: rev.data_format.into(), modified_time: rev.modified_time, create_time: rev.create_time, layout: rev.layout.into(), @@ -49,31 +49,34 @@ impl std::convert::From for ViewPB { } #[derive(Eq, PartialEq, Hash, Debug, ProtoBuf_Enum, Clone)] -pub enum ViewDataTypePB { - Text = 0, - Database = 1, +pub enum ViewDataFormatPB { + DeltaFormat = 0, + DatabaseFormat = 1, + TreeFormat = 2, } -impl std::default::Default for ViewDataTypePB { +impl std::default::Default for ViewDataFormatPB { fn default() -> Self { - ViewDataTypeRevision::default().into() + ViewDataFormatRevision::default().into() } } -impl std::convert::From for ViewDataTypePB { - fn from(rev: ViewDataTypeRevision) -> Self { +impl std::convert::From for ViewDataFormatPB { + fn from(rev: ViewDataFormatRevision) -> Self { match rev { - ViewDataTypeRevision::Text => ViewDataTypePB::Text, - ViewDataTypeRevision::Database => ViewDataTypePB::Database, + ViewDataFormatRevision::DeltaFormat => ViewDataFormatPB::DeltaFormat, + ViewDataFormatRevision::DatabaseFormat => ViewDataFormatPB::DatabaseFormat, + ViewDataFormatRevision::TreeFormat => ViewDataFormatPB::TreeFormat, } } } -impl std::convert::From for ViewDataTypeRevision { - fn from(ty: ViewDataTypePB) -> Self { +impl std::convert::From for ViewDataFormatRevision { + fn from(ty: ViewDataFormatPB) -> Self { match ty { - ViewDataTypePB::Text => ViewDataTypeRevision::Text, - ViewDataTypePB::Database => ViewDataTypeRevision::Database, + ViewDataFormatPB::DeltaFormat => ViewDataFormatRevision::DeltaFormat, + ViewDataFormatPB::DatabaseFormat => ViewDataFormatRevision::DatabaseFormat, + ViewDataFormatPB::TreeFormat => ViewDataFormatRevision::TreeFormat, } } } @@ -146,7 +149,7 @@ pub struct CreateViewPayloadPB { pub thumbnail: Option, #[pb(index = 5)] - pub data_type: ViewDataTypePB, + pub data_format: ViewDataFormatPB, #[pb(index = 6)] pub layout: ViewLayoutTypePB, @@ -161,7 +164,7 @@ pub struct CreateViewParams { pub name: String, pub desc: String, pub thumbnail: String, - pub data_type: ViewDataTypePB, + pub data_format: ViewDataFormatPB, pub layout: ViewLayoutTypePB, pub view_id: String, pub view_content_data: Vec, @@ -183,7 +186,7 @@ impl TryInto for CreateViewPayloadPB { belong_to_id, name, desc: self.desc, - data_type: self.data_type, + data_format: self.data_format, layout: self.layout, thumbnail, view_id, diff --git a/frontend/rust-lib/flowy-folder/src/entities/view_info.rs b/frontend/rust-lib/flowy-folder/src/entities/view_info.rs index 42dbc42517..e4031d0ecb 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/view_info.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/view_info.rs @@ -1,4 +1,4 @@ -use crate::entities::{RepeatedViewPB, ViewDataTypePB}; +use crate::entities::{RepeatedViewPB, ViewDataFormatPB}; use flowy_derive::ProtoBuf; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] @@ -16,7 +16,7 @@ pub struct ViewInfoPB { pub desc: String, #[pb(index = 5)] - pub data_type: ViewDataTypePB, + pub data_type: ViewDataFormatPB, #[pb(index = 6)] pub belongings: RepeatedViewPB, diff --git a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs index 20c9750940..f3f5742476 100644 --- a/frontend/rust-lib/flowy-folder/src/entities/workspace.rs +++ b/frontend/rust-lib/flowy-folder/src/entities/workspace.rs @@ -5,7 +5,7 @@ use crate::{ impl_def_and_def_mut, }; use flowy_derive::ProtoBuf; -use flowy_folder_data_model::revision::WorkspaceRevision; +use folder_rev_model::WorkspaceRevision; use std::convert::TryInto; #[derive(Eq, PartialEq, ProtoBuf, Default, Debug, Clone)] @@ -92,7 +92,7 @@ impl WorkspaceIdPB { } #[derive(Default, ProtoBuf, Clone)] -pub struct CurrentWorkspaceSettingPB { +pub struct WorkspaceSettingPB { #[pb(index = 1)] pub workspace: WorkspacePB, diff --git a/frontend/rust-lib/flowy-folder/src/event_map.rs b/frontend/rust-lib/flowy-folder/src/event_map.rs index 71e436b848..ba9b3b74e4 100644 --- a/frontend/rust-lib/flowy-folder/src/event_map.rs +++ b/frontend/rust-lib/flowy-folder/src/event_map.rs @@ -11,7 +11,7 @@ use crate::{ }; use flowy_database::{ConnectionPool, DBConnection}; use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; -use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use folder_rev_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use lib_dispatch::prelude::*; use lib_infra::future::FutureResult; use std::sync::Arc; @@ -46,7 +46,7 @@ pub fn create(folder: Arc) -> Module { // Workspace module = module .event(FolderEvent::CreateWorkspace, create_workspace_handler) - .event(FolderEvent::ReadCurWorkspace, read_cur_workspace_handler) + .event(FolderEvent::ReadCurrentWorkspace, read_cur_workspace_handler) .event(FolderEvent::ReadWorkspaces, read_workspaces_handler) .event(FolderEvent::OpenWorkspace, open_workspace_handler) .event(FolderEvent::ReadWorkspaceApps, read_workspace_apps_handler); @@ -87,8 +87,8 @@ pub enum FolderEvent { #[event(input = "CreateWorkspacePayloadPB", output = "WorkspacePB")] CreateWorkspace = 0, - #[event(output = "CurrentWorkspaceSettingPB")] - ReadCurWorkspace = 1, + #[event(output = "WorkspaceSettingPB")] + ReadCurrentWorkspace = 1, #[event(input = "WorkspaceIdPB", output = "RepeatedWorkspacePB")] ReadWorkspaces = 2, @@ -126,7 +126,7 @@ pub enum FolderEvent { #[event(input = "RepeatedViewIdPB")] DeleteView = 204, - #[event(input = "ViewIdPB")] + #[event(input = "ViewPB")] DuplicateView = 205, #[event(input = "ViewIdPB")] diff --git a/frontend/rust-lib/flowy-folder/src/manager.rs b/frontend/rust-lib/flowy-folder/src/manager.rs index 61bd3c77b6..f0152aed3e 100644 --- a/frontend/rust-lib/flowy-folder/src/manager.rs +++ b/frontend/rust-lib/flowy-folder/src/manager.rs @@ -1,6 +1,6 @@ -use crate::entities::view::ViewDataTypePB; -use crate::entities::ViewLayoutTypePB; -use crate::services::folder_editor::FolderRevisionCompactor; +use crate::entities::view::ViewDataFormatPB; +use crate::entities::{ViewLayoutTypePB, ViewPB}; +use crate::services::folder_editor::FolderRevisionCompress; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, entities::workspace::RepeatedWorkspacePB, @@ -12,14 +12,19 @@ use crate::{ }, }; use bytes::Bytes; +use flowy_document::editor::initial_read_me; use flowy_error::FlowyError; -use flowy_folder_data_model::user_default; -use flowy_revision::disk::SQLiteDocumentRevisionPersistence; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence}; -use flowy_sync::client_document::default::{initial_document_str, initial_read_me}; -use flowy_sync::{client_folder::FolderPad, entities::ws_data::ServerRevisionWSData}; +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, + SQLiteRevisionSnapshotPersistence, +}; +use folder_rev_model::user_default; use lazy_static::lazy_static; use lib_infra::future::FutureResult; + +use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; +use flowy_http_model::ws_data::ServerRevisionWSData; +use flowy_sync::client_folder::FolderPad; use std::{collections::HashMap, convert::TryInto, fmt::Formatter, sync::Arc}; use tokio::sync::RwLock as TokioRwLock; lazy_static! { @@ -63,7 +68,6 @@ pub struct FolderManager { pub(crate) trash_controller: Arc, web_socket: Arc, folder_editor: Arc>>>, - data_processors: ViewDataProcessorMap, } impl FolderManager { @@ -94,7 +98,7 @@ impl FolderManager { persistence.clone(), cloud_service.clone(), trash_controller.clone(), - data_processors.clone(), + data_processors, )); let app_controller = Arc::new(AppController::new( @@ -121,7 +125,6 @@ impl FolderManager { trash_controller, web_socket, folder_editor, - data_processors, } } @@ -150,6 +153,7 @@ impl FolderManager { } } + /// Called immediately after the application launched with the user sign in/sign up. #[tracing::instrument(level = "trace", skip(self), err)] pub async fn initialize(&self, user_id: &str, token: &str) -> FlowyResult<()> { let mut write_guard = INIT_FOLDER_FLAG.write().await; @@ -164,9 +168,10 @@ impl FolderManager { let pool = self.persistence.db_pool()?; let object_id = folder_id.as_ref(); - let disk_cache = SQLiteDocumentRevisionPersistence::new(user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache); - let rev_compactor = FolderRevisionCompactor(); + let disk_cache = SQLiteFolderRevisionPersistence::new(user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(100, false); + let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache, configuration); + let rev_compactor = FolderRevisionCompress(); // let history_persistence = SQLiteRevisionHistoryPersistence::new(object_id, pool.clone()); let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(object_id, pool); let rev_manager = RevisionManager::new( @@ -183,17 +188,24 @@ impl FolderManager { let _ = self.app_controller.initialize()?; let _ = self.view_controller.initialize()?; - - self.data_processors.iter().for_each(|(_, processor)| { - processor.initialize(); - }); - write_guard.insert(user_id.to_owned(), true); Ok(()) } - pub async fn initialize_with_new_user(&self, user_id: &str, token: &str) -> FlowyResult<()> { - DefaultFolderBuilder::build(token, user_id, self.persistence.clone(), self.view_controller.clone()).await?; + pub async fn initialize_with_new_user( + &self, + user_id: &str, + token: &str, + view_data_format: ViewDataFormatPB, + ) -> FlowyResult<()> { + DefaultFolderBuilder::build( + token, + user_id, + self.persistence.clone(), + self.view_controller.clone(), + || (view_data_format.clone(), Bytes::from(initial_read_me())), + ) + .await?; self.initialize(user_id, token).await } @@ -204,27 +216,26 @@ impl FolderManager { struct DefaultFolderBuilder(); impl DefaultFolderBuilder { - async fn build( + async fn build (ViewDataFormatPB, Bytes)>( token: &str, user_id: &str, persistence: Arc, view_controller: Arc, + create_view_fn: F, ) -> FlowyResult<()> { log::debug!("Create user default workspace"); let workspace_rev = user_default::create_default_workspace(); set_current_workspace(&workspace_rev.id); for app in workspace_rev.apps.iter() { for (index, view) in app.belongings.iter().enumerate() { - let view_data = if index == 0 { - initial_read_me().json_str() - } else { - initial_document_str() - }; - let _ = view_controller.set_latest_view(&view.id); - let layout_type = ViewLayoutTypePB::from(view.layout.clone()); - let _ = view_controller - .create_view(&view.id, ViewDataTypePB::Text, layout_type, Bytes::from(view_data)) - .await?; + let (view_data_type, view_data) = create_view_fn(); + if index == 0 { + let _ = view_controller.set_latest_view(&view.id); + let layout_type = ViewLayoutTypePB::from(view.layout.clone()); + let _ = view_controller + .create_view(&view.id, view_data_type, layout_type, view_data) + .await?; + } } } let folder = FolderPad::new(vec![workspace_rev.clone()], vec![])?; @@ -248,25 +259,24 @@ impl FolderManager { } pub trait ViewDataProcessor { - fn initialize(&self) -> FutureResult<(), FlowyError>; - - fn create_container( + fn create_view( &self, user_id: &str, view_id: &str, layout: ViewLayoutTypePB, - delta_data: Bytes, + view_data: Bytes, ) -> FutureResult<(), FlowyError>; - fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError>; + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>; - fn get_view_data(&self, view_id: &str) -> FutureResult; + fn get_view_data(&self, view: &ViewPB) -> FutureResult; fn create_default_view( &self, user_id: &str, view_id: &str, layout: ViewLayoutTypePB, + data_format: ViewDataFormatPB, ) -> FutureResult; fn create_view_from_delta_data( @@ -277,7 +287,7 @@ pub trait ViewDataProcessor { layout: ViewLayoutTypePB, ) -> FutureResult; - fn data_type(&self) -> ViewDataTypePB; + fn data_types(&self) -> Vec; } -pub type ViewDataProcessorMap = Arc>>; +pub type ViewDataProcessorMap = Arc>>; diff --git a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs index 375e696bd1..d2592b69ef 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/controller.rs @@ -12,7 +12,7 @@ use crate::{ }, }; -use flowy_folder_data_model::revision::AppRevision; +use folder_rev_model::AppRevision; use futures::{FutureExt, StreamExt}; use std::{collections::HashSet, sync::Arc}; diff --git a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs index 1594351015..d3c7dd828a 100644 --- a/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/app/event_handler.rs @@ -3,7 +3,7 @@ use crate::{ errors::FlowyError, services::{AppController, TrashController, ViewController}, }; -use flowy_folder_data_model::revision::TrashRevision; +use folder_rev_model::TrashRevision; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::{convert::TryInto, sync::Arc}; @@ -34,7 +34,7 @@ pub(crate) async fn delete_app_handler( Ok(()) } -#[tracing::instrument(level = "debug", skip(data, controller))] +#[tracing::instrument(level = "trace", skip(data, controller))] pub(crate) async fn update_app_handler( data: Data, controller: AppData>, @@ -44,7 +44,7 @@ pub(crate) async fn update_app_handler( Ok(()) } -#[tracing::instrument(level = "info", skip(data, app_controller, view_controller), err)] +#[tracing::instrument(level = "trace", skip(data, app_controller, view_controller), err)] pub(crate) async fn read_app_handler( data: Data, app_controller: AppData>, diff --git a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs index b66b649cc3..def261b4e9 100644 --- a/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs +++ b/frontend/rust-lib/flowy-folder/src/services/folder_editor.rs @@ -1,27 +1,27 @@ use crate::manager::FolderId; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; +use flowy_http_model::revision::Revision; +use flowy_http_model::ws_data::ServerRevisionWSData; use flowy_revision::{ - RevisionCloudService, RevisionCompress, RevisionManager, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, RevisionWebSocket, }; +use flowy_sync::client_folder::{FolderChangeset, FolderPad}; use flowy_sync::util::make_operations_from_revisions; -use flowy_sync::{ - client_folder::{FolderChangeset, FolderPad}, - entities::{revision::Revision, ws_data::ServerRevisionWSData}, -}; use lib_infra::future::FutureResult; - use lib_ot::core::EmptyAttributes; use parking_lot::RwLock; use std::sync::Arc; pub struct FolderEditor { + #[allow(dead_code)] user_id: String, #[allow(dead_code)] - pub(crate) folder_id: FolderId, + folder_id: FolderId, pub(crate) folder: Arc>, - rev_manager: Arc, + rev_manager: Arc>>, #[cfg(feature = "sync")] ws_manager: Arc, } @@ -32,13 +32,15 @@ impl FolderEditor { user_id: &str, folder_id: &FolderId, token: &str, - mut rev_manager: RevisionManager, + mut rev_manager: RevisionManager>, web_socket: Arc, ) -> FlowyResult { let cloud = Arc::new(FolderRevisionCloudService { token: token.to_string(), }); - let folder = Arc::new(RwLock::new(rev_manager.load::(Some(cloud)).await?)); + let folder = Arc::new(RwLock::new( + rev_manager.initialize::(Some(cloud)).await?, + )); let rev_manager = Arc::new(rev_manager); #[cfg(feature = "sync")] @@ -82,14 +84,7 @@ impl FolderEditor { let FolderChangeset { operations: delta, md5 } = change; let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); let delta_data = delta.json_bytes(); - let revision = Revision::new( - &self.rev_manager.object_id, - base_rev_id, - rev_id, - delta_data, - &self.user_id, - md5, - ); + let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, delta_data, md5); let _ = futures::executor::block_on(async { self.rev_manager.add_local_revision(&revision).await })?; Ok(()) } @@ -112,16 +107,16 @@ impl RevisionObjectDeserializer for FolderRevisionSerde { } impl RevisionObjectSerializer for FolderRevisionSerde { - fn serialize_revisions(revisions: Vec) -> FlowyResult { + fn combine_revisions(revisions: Vec) -> FlowyResult { let operations = make_operations_from_revisions::(revisions)?; Ok(operations.json_bytes()) } } -pub struct FolderRevisionCompactor(); -impl RevisionCompress for FolderRevisionCompactor { - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult { - FolderRevisionSerde::serialize_revisions(revisions) +pub struct FolderRevisionCompress(); +impl RevisionMergeable for FolderRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + FolderRevisionSerde::combine_revisions(revisions) } } @@ -139,7 +134,7 @@ impl RevisionCloudService for FolderRevisionCloudService { #[cfg(feature = "flowy_unit_test")] impl FolderEditor { - pub fn rev_manager(&self) -> Arc { + pub fn rev_manager(&self) -> Arc>> { self.rev_manager.clone() } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs index cfa034240e..e65c867400 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/migration.rs @@ -6,13 +6,15 @@ use crate::{ use bytes::Bytes; use flowy_database::kv::KV; use flowy_error::{FlowyError, FlowyResult}; -use flowy_folder_data_model::revision::{AppRevision, FolderRevision, ViewRevision, WorkspaceRevision}; -use flowy_revision::disk::SQLiteDocumentRevisionPersistence; +use flowy_http_model::revision::Revision; use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; use flowy_sync::client_folder::make_folder_rev_json_str; -use flowy_sync::entities::revision::Revision; -use flowy_sync::{client_folder::FolderPad, entities::revision::md5}; -use lib_ot::core::DeltaBuilder; +use flowy_sync::client_folder::FolderPad; +use flowy_sync::server_folder::FolderOperationsBuilder; +use folder_rev_model::{AppRevision, FolderRevision, ViewRevision, WorkspaceRevision}; + +use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; +use flowy_http_model::util::md5; use std::sync::Arc; const V1_MIGRATION: &str = "FOLDER_V1_MIGRATION"; @@ -102,7 +104,7 @@ impl FolderMigration { } let _ = self.migration_folder_rev_struct(folder_id).await?; KV::set_bool(&key, true); - tracing::info!("Run folder v3 migration"); + tracing::trace!("Run folder v3 migration"); Ok(()) } @@ -112,7 +114,7 @@ impl FolderMigration { }; let pool = self.database.db_pool()?; - let disk_cache = SQLiteDocumentRevisionPersistence::new(&self.user_id, pool); + let disk_cache = SQLiteFolderRevisionPersistence::new(&self.user_id, pool); let reset = RevisionStructReset::new(&self.user_id, object, Arc::new(disk_cache)); reset.run().await } @@ -134,7 +136,7 @@ impl RevisionResettable for FolderRevisionResettable { fn reset_data(&self, revisions: Vec) -> FlowyResult { let pad = FolderPad::from_revisions(revisions)?; let json = pad.to_json()?; - let bytes = DeltaBuilder::new().insert(&json).build().json_bytes(); + let bytes = FolderOperationsBuilder::new().insert(&json).build().json_bytes(); Ok(bytes) } @@ -143,4 +145,12 @@ impl RevisionResettable for FolderRevisionResettable { let json = make_folder_rev_json_str(&folder)?; Ok(json) } + + fn read_record(&self) -> Option { + KV::get_str(self.target_id()) + } + + fn set_record(&self, record: String) { + KV::set_str(self.target_id(), record); + } } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs index 1e0c5e9b28..82cbb98ff2 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/mod.rs @@ -1,4 +1,5 @@ mod migration; +pub mod rev_sqlite; pub mod version_1; mod version_2; @@ -9,11 +10,13 @@ use crate::{ }; use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; -use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; -use flowy_revision::disk::{RevisionRecord, RevisionState}; -use flowy_revision::mk_text_block_revision_disk_cache; -use flowy_sync::{client_folder::FolderPad, entities::revision::Revision}; -use lib_ot::core::DeltaBuilder; +use flowy_http_model::revision::Revision; +use flowy_revision::disk::{RevisionDiskCache, RevisionState, SyncRecord}; +use flowy_sync::client_folder::FolderPad; +use folder_rev_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; + +use crate::services::persistence::rev_sqlite::SQLiteFolderRevisionPersistence; +use flowy_sync::server_folder::FolderOperationsBuilder; use std::sync::Arc; use tokio::sync::RwLock; pub use version_1::{app_sql::*, trash_sql::*, v1_impl::V1Transaction, view_sql::*, workspace_sql::*}; @@ -35,7 +38,7 @@ pub trait FolderPersistenceTransaction { fn read_view(&self, view_id: &str) -> FlowyResult; fn read_views(&self, belong_to_id: &str) -> FlowyResult>; fn update_view(&self, changeset: ViewChangeset) -> FlowyResult<()>; - fn delete_view(&self, view_id: &str) -> FlowyResult<()>; + fn delete_view(&self, view_id: &str) -> FlowyResult; fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()>; fn create_trash(&self, trashes: Vec) -> FlowyResult<()>; @@ -108,9 +111,9 @@ impl FolderPersistence { pub async fn save_folder(&self, user_id: &str, folder_id: &FolderId, folder: FolderPad) -> FlowyResult<()> { let pool = self.database.db_pool()?; let json = folder.to_json()?; - let delta_data = DeltaBuilder::new().insert(&json).build().json_bytes(); - let revision = Revision::initial_revision(user_id, folder_id.as_ref(), delta_data); - let record = RevisionRecord { + let delta_data = FolderOperationsBuilder::new().insert(&json).build().json_bytes(); + let revision = Revision::initial_revision(folder_id.as_ref(), delta_data); + let record = SyncRecord { revision, state: RevisionState::Sync, write_to_disk: true, @@ -120,3 +123,10 @@ impl FolderPersistence { disk_cache.delete_and_insert_records(folder_id.as_ref(), None, vec![record]) } } + +pub fn mk_text_block_revision_disk_cache( + user_id: &str, + pool: Arc, +) -> Arc, Error = FlowyError>> { + Arc::new(SQLiteFolderRevisionPersistence::new(user_id, pool)) +} diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/document_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs similarity index 77% rename from frontend/rust-lib/flowy-revision/src/cache/disk/document_impl.rs rename to frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs index 5866df3523..7c2437cd5e 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/document_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/folder_rev_sqlite.rs @@ -1,5 +1,3 @@ -use crate::cache::disk::RevisionDiskCache; -use crate::disk::{RevisionChangeset, RevisionRecord}; use bytes::Bytes; use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_database::{ @@ -9,33 +7,36 @@ use flowy_database::{ ConnectionPool, }; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_sync::{ - entities::revision::{RevType, Revision, RevisionRange}, - util::md5, -}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use std::sync::Arc; -pub struct SQLiteDocumentRevisionPersistence { +pub struct SQLiteFolderRevisionPersistence { user_id: String, pub(crate) pool: Arc, } -impl RevisionDiskCache for SQLiteDocumentRevisionPersistence { +impl RevisionDiskCache> for SQLiteFolderRevisionPersistence { type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; - let _ = TextRevisionSql::create(revision_records, &*conn)?; + let _ = FolderRevisionSql::create(revision_records, &*conn)?; Ok(()) } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + fn read_revision_records( &self, object_id: &str, rev_ids: Option>, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = self.pool.get().map_err(internal_error)?; - let records = TextRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; + let records = FolderRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; Ok(records) } @@ -43,9 +44,9 @@ impl RevisionDiskCache for SQLiteDocumentRevisionPersistence { &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = &*self.pool.get().map_err(internal_error)?; - let revisions = TextRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; + let revisions = FolderRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; Ok(revisions) } @@ -53,7 +54,7 @@ impl RevisionDiskCache for SQLiteDocumentRevisionPersistence { let conn = &*self.pool.get().map_err(internal_error)?; let _ = conn.immediate_transaction::<_, FlowyError, _>(|| { for changeset in changesets { - let _ = TextRevisionSql::update(changeset, conn)?; + let _ = FolderRevisionSql::update(changeset, conn)?; } Ok(()) })?; @@ -62,7 +63,7 @@ impl RevisionDiskCache for SQLiteDocumentRevisionPersistence { fn delete_revision_records(&self, object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { let conn = &*self.pool.get().map_err(internal_error)?; - let _ = TextRevisionSql::delete(object_id, rev_ids, conn)?; + let _ = FolderRevisionSql::delete(object_id, rev_ids, conn)?; Ok(()) } @@ -70,18 +71,18 @@ impl RevisionDiskCache for SQLiteDocumentRevisionPersistence { &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; conn.immediate_transaction::<_, FlowyError, _>(|| { - let _ = TextRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?; - let _ = TextRevisionSql::create(inserted_records, &*conn)?; + let _ = FolderRevisionSql::delete(object_id, deleted_rev_ids, &*conn)?; + let _ = FolderRevisionSql::create(inserted_records, &*conn)?; Ok(()) }) } } -impl SQLiteDocumentRevisionPersistence { +impl SQLiteFolderRevisionPersistence { pub fn new(user_id: &str, pool: Arc) -> Self { Self { user_id: user_id.to_owned(), @@ -90,10 +91,10 @@ impl SQLiteDocumentRevisionPersistence { } } -struct TextRevisionSql {} +struct FolderRevisionSql {} -impl TextRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { +impl FolderRevisionSql { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { // Batch insert: https://diesel.rs/guides/all-about-inserts.html let records = revision_records @@ -139,7 +140,7 @@ impl TextRevisionSql { object_id: &str, rev_ids: Option>, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let mut sql = dsl::rev_table.filter(dsl::doc_id.eq(object_id)).into_boxed(); if let Some(rev_ids) = rev_ids { sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); @@ -158,7 +159,7 @@ impl TextRevisionSql { object_id: &str, range: RevisionRange, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let rev_tables = dsl::rev_table .filter(dsl::rev_id.ge(range.start)) .filter(dsl::rev_id.le(range.end)) @@ -216,17 +217,16 @@ impl std::default::Default for TextRevisionState { } } -fn mk_revision_record_from_table(user_id: &str, table: RevisionTable) -> RevisionRecord { +fn mk_revision_record_from_table(_user_id: &str, table: RevisionTable) -> SyncRecord { let md5 = md5(&table.data); let revision = Revision::new( &table.doc_id, table.base_rev_id, table.rev_id, Bytes::from(table.data), - user_id, md5, ); - RevisionRecord { + SyncRecord { revision, state: table.state.into(), write_to_disk: false, @@ -260,21 +260,3 @@ impl std::convert::From for RevTableType { } } } - -impl std::convert::From for RevTableType { - fn from(ty: RevType) -> Self { - match ty { - RevType::DeprecatedLocal => RevTableType::Local, - RevType::DeprecatedRemote => RevTableType::Remote, - } - } -} - -impl std::convert::From for RevType { - fn from(ty: RevTableType) -> Self { - match ty { - RevTableType::Local => RevType::DeprecatedLocal, - RevTableType::Remote => RevType::DeprecatedRemote, - } - } -} diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs new file mode 100644 index 0000000000..ff986cb0a9 --- /dev/null +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/rev_sqlite/mod.rs @@ -0,0 +1,2 @@ +mod folder_rev_sqlite; +pub use folder_rev_sqlite::*; diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs index cf643e7dff..91c1ab84a7 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/app_sql.rs @@ -8,7 +8,7 @@ use flowy_database::{ schema::{app_table, app_table::dsl}, SqliteConnection, }; -use flowy_folder_data_model::revision::AppRevision; +use folder_rev_model::AppRevision; pub struct AppTableSql(); impl AppTableSql { diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs index 2e69a4ba1c..e073a17c9b 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/trash_sql.rs @@ -5,7 +5,7 @@ use flowy_database::{ schema::{trash_table, trash_table::dsl}, SqliteConnection, }; -use flowy_folder_data_model::revision::{TrashRevision, TrashTypeRevision}; +use folder_rev_model::{TrashRevision, TrashTypeRevision}; pub struct TrashTableSql(); impl TrashTableSql { diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs index f73c8c9e61..ec0deb9a27 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/v1_impl.rs @@ -8,7 +8,7 @@ use crate::services::persistence::{ }; use flowy_database::DBConnection; use flowy_error::FlowyResult; -use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use folder_rev_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; /// V1Transaction is deprecated since version 0.0.2 version pub struct V1Transaction<'a>(pub &'a DBConnection); @@ -84,9 +84,10 @@ impl<'a> FolderPersistenceTransaction for V1Transaction<'a> { Ok(()) } - fn delete_view(&self, view_id: &str) -> FlowyResult<()> { + fn delete_view(&self, view_id: &str) -> FlowyResult { + let view_revision: ViewRevision = ViewTableSql::read_view(view_id, &*self.0)?.into(); let _ = ViewTableSql::delete_view(view_id, &*self.0)?; - Ok(()) + Ok(view_revision) } fn move_view(&self, _view_id: &str, _from: usize, _to: usize) -> FlowyResult<()> { @@ -182,7 +183,7 @@ where (**self).update_view(changeset) } - fn delete_view(&self, view_id: &str) -> FlowyResult<()> { + fn delete_view(&self, view_id: &str) -> FlowyResult { (**self).delete_view(view_id) } diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs index c1b21cbe32..d3088be1d5 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/view_sql.rs @@ -13,7 +13,7 @@ use flowy_database::{ SqliteConnection, }; -use flowy_folder_data_model::revision::{ViewDataTypeRevision, ViewLayoutTypeRevision, ViewRevision}; +use folder_rev_model::{ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision}; use lib_infra::util::timestamp; pub struct ViewTableSql(); @@ -78,7 +78,7 @@ pub(crate) struct ViewTable { pub modified_time: i64, pub create_time: i64, pub thumbnail: String, - pub view_type: SqlViewDataType, + pub view_type: SqlViewDataFormat, pub version: i64, pub is_trash: bool, pub ext_data: String, @@ -86,9 +86,10 @@ pub(crate) struct ViewTable { impl ViewTable { pub fn new(view_rev: ViewRevision) -> Self { - let data_type = match view_rev.data_type { - ViewDataTypeRevision::Text => SqlViewDataType::Block, - ViewDataTypeRevision::Database => SqlViewDataType::Grid, + let data_type = match view_rev.data_format { + ViewDataFormatRevision::DeltaFormat => SqlViewDataFormat::Delta, + ViewDataFormatRevision::DatabaseFormat => SqlViewDataFormat::Database, + ViewDataFormatRevision::TreeFormat => SqlViewDataFormat::Tree, }; ViewTable { @@ -110,8 +111,9 @@ impl ViewTable { impl std::convert::From for ViewRevision { fn from(table: ViewTable) -> Self { let data_type = match table.view_type { - SqlViewDataType::Block => ViewDataTypeRevision::Text, - SqlViewDataType::Grid => ViewDataTypeRevision::Database, + SqlViewDataFormat::Delta => ViewDataFormatRevision::DeltaFormat, + SqlViewDataFormat::Database => ViewDataFormatRevision::DatabaseFormat, + SqlViewDataFormat::Tree => ViewDataFormatRevision::TreeFormat, }; ViewRevision { @@ -119,7 +121,7 @@ impl std::convert::From for ViewRevision { app_id: table.belong_to_id, name: table.name, desc: table.desc, - data_type, + data_format: data_type, belongings: vec![], modified_time: table.modified_time, version: table.version, @@ -180,34 +182,36 @@ impl ViewChangeset { #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, FromSqlRow, AsExpression)] #[repr(i32)] #[sql_type = "Integer"] -pub enum SqlViewDataType { - Block = 0, - Grid = 1, +pub enum SqlViewDataFormat { + Delta = 0, + Database = 1, + Tree = 2, } -impl std::default::Default for SqlViewDataType { +impl std::default::Default for SqlViewDataFormat { fn default() -> Self { - SqlViewDataType::Block + SqlViewDataFormat::Delta } } -impl std::convert::From for SqlViewDataType { +impl std::convert::From for SqlViewDataFormat { fn from(value: i32) -> Self { match value { - 0 => SqlViewDataType::Block, - 1 => SqlViewDataType::Grid, + 0 => SqlViewDataFormat::Delta, + 1 => SqlViewDataFormat::Database, + 2 => SqlViewDataFormat::Tree, o => { log::error!("Unsupported view type {}, fallback to ViewType::Block", o); - SqlViewDataType::Block + SqlViewDataFormat::Delta } } } } -impl SqlViewDataType { +impl SqlViewDataFormat { pub fn value(&self) -> i32 { *self as i32 } } -impl_sql_integer_expression!(SqlViewDataType); +impl_sql_integer_expression!(SqlViewDataFormat); diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs index 4de8f7df34..69fccee56f 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_1/workspace_sql.rs @@ -4,7 +4,7 @@ use flowy_database::{ prelude::*, schema::{workspace_table, workspace_table::dsl}, }; -use flowy_folder_data_model::revision::WorkspaceRevision; +use folder_rev_model::WorkspaceRevision; pub(crate) struct WorkspaceTableSql(); impl WorkspaceTableSql { diff --git a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs index 44d63cf660..1ae270e226 100644 --- a/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs +++ b/frontend/rust-lib/flowy-folder/src/services/persistence/version_2/v2_impl.rs @@ -3,7 +3,7 @@ use crate::services::{ persistence::{AppChangeset, FolderPersistenceTransaction, ViewChangeset, WorkspaceChangeset}, }; use flowy_error::{FlowyError, FlowyResult}; -use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use folder_rev_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use std::sync::Arc; impl FolderPersistenceTransaction for FolderEditor { @@ -113,11 +113,12 @@ impl FolderPersistenceTransaction for FolderEditor { Ok(()) } - fn delete_view(&self, view_id: &str) -> FlowyResult<()> { - if let Some(change) = self.folder.write().delete_view(view_id)? { + fn delete_view(&self, view_id: &str) -> FlowyResult { + let view = self.folder.read().read_view(view_id)?; + if let Some(change) = self.folder.write().delete_view(&view.app_id, view_id)? { let _ = self.apply_change(change)?; } - Ok(()) + Ok(view) } fn move_view(&self, view_id: &str, from: usize, to: usize) -> FlowyResult<()> { @@ -207,7 +208,7 @@ where (**self).update_view(changeset) } - fn delete_view(&self, view_id: &str) -> FlowyResult<()> { + fn delete_view(&self, view_id: &str) -> FlowyResult { (**self).delete_view(view_id) } diff --git a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs index 8ac9618917..1588190d38 100644 --- a/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/trash/controller.rs @@ -6,7 +6,7 @@ use crate::{ services::persistence::{FolderPersistence, FolderPersistenceTransaction}, }; -use flowy_folder_data_model::revision::TrashRevision; +use folder_rev_model::TrashRevision; use std::{fmt::Formatter, sync::Arc}; use tokio::sync::{broadcast, mpsc}; diff --git a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs index b77a44623d..43a9a07339 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/controller.rs @@ -1,5 +1,5 @@ -pub use crate::entities::view::ViewDataTypePB; -use crate::entities::{DeletedViewPB, ViewInfoPB, ViewLayoutTypePB}; +pub use crate::entities::view::ViewDataFormatPB; +use crate::entities::{AppPB, DeletedViewPB, ViewInfoPB, ViewLayoutTypePB}; use crate::manager::{ViewDataProcessor, ViewDataProcessorMap}; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, @@ -16,8 +16,8 @@ use crate::{ }; use bytes::Bytes; use flowy_database::kv::KV; -use flowy_folder_data_model::revision::{gen_view_id, ViewRevision}; -use flowy_sync::entities::document::DocumentIdPB; +use flowy_http_model::document::DocumentIdPB; +use folder_rev_model::{gen_view_id, ViewRevision}; use futures::{FutureExt, StreamExt}; use std::{collections::HashSet, sync::Arc}; @@ -58,12 +58,17 @@ impl ViewController { &self, mut params: CreateViewParams, ) -> Result { - let processor = self.get_data_processor(params.data_type.clone())?; + let processor = self.get_data_processor(params.data_format.clone())?; let user_id = self.user.user_id()?; if params.view_content_data.is_empty() { tracing::trace!("Create view with build-in data"); let view_data = processor - .create_default_view(&user_id, ¶ms.view_id, params.layout.clone()) + .create_default_view( + &user_id, + ¶ms.view_id, + params.layout.clone(), + params.data_format.clone(), + ) .await?; params.view_content_data = view_data.to_vec(); } else { @@ -79,7 +84,7 @@ impl ViewController { let _ = self .create_view( ¶ms.view_id, - params.data_type.clone(), + params.data_format.clone(), params.layout.clone(), delta_data, ) @@ -91,22 +96,20 @@ impl ViewController { Ok(view_rev) } - #[tracing::instrument(level = "debug", skip(self, view_id, delta_data), err)] + #[tracing::instrument(level = "debug", skip(self, view_id, view_data), err)] pub(crate) async fn create_view( &self, view_id: &str, - data_type: ViewDataTypePB, + data_type: ViewDataFormatPB, layout_type: ViewLayoutTypePB, - delta_data: Bytes, + view_data: Bytes, ) -> Result<(), FlowyError> { - if delta_data.is_empty() { + if view_data.is_empty() { return Err(FlowyError::internal().context("The content of the view should not be empty")); } let user_id = self.user.user_id()?; let processor = self.get_data_processor(data_type)?; - let _ = processor - .create_container(&user_id, view_id, layout_type, delta_data) - .await?; + let _ = processor.create_view(&user_id, view_id, layout_type, view_data).await?; Ok(()) } @@ -156,7 +159,7 @@ impl ViewController { belong_to_id: view_rev.app_id, name: view_rev.name, desc: view_rev.desc, - data_type: view_rev.data_type.into(), + data_type: view_rev.data_format.into(), belongings: RepeatedViewPB { items }, ext_data: view_rev.ext_data, }; @@ -188,7 +191,7 @@ impl ViewController { #[tracing::instrument(level = "debug", skip(self), err)] pub(crate) async fn close_view(&self, view_id: &str) -> Result<(), FlowyError> { let processor = self.get_data_processor_from_view_id(view_id).await?; - let _ = processor.close_container(view_id).await?; + let _ = processor.close_view(view_id).await?; Ok(()) } @@ -223,7 +226,7 @@ impl ViewController { .send(); let processor = self.get_data_processor_from_view_id(&view_id).await?; - let _ = processor.close_container(&view_id).await?; + let _ = processor.close_view(&view_id).await?; Ok(()) } @@ -242,20 +245,20 @@ impl ViewController { } #[tracing::instrument(level = "debug", skip(self), err)] - pub(crate) async fn duplicate_view(&self, view_id: &str) -> Result<(), FlowyError> { + pub(crate) async fn duplicate_view(&self, view: ViewPB) -> Result<(), FlowyError> { let view_rev = self .persistence - .begin_transaction(|transaction| transaction.read_view(view_id)) + .begin_transaction(|transaction| transaction.read_view(&view.id)) .await?; - let processor = self.get_data_processor(view_rev.data_type.clone())?; - let view_data = processor.get_view_data(view_id).await?; + let processor = self.get_data_processor(view_rev.data_format.clone())?; + let view_data = processor.get_view_data(&view).await?; let duplicate_params = CreateViewParams { belong_to_id: view_rev.app_id.clone(), name: format!("{} (copy)", &view_rev.name), desc: view_rev.desc, thumbnail: view_rev.thumbnail, - data_type: view_rev.data_type.into(), + data_format: view_rev.data_format.into(), layout: view_rev.layout.into(), view_content_data: view_data.to_vec(), view_id: gen_view_id(), @@ -399,11 +402,11 @@ impl ViewController { .persistence .begin_transaction(|transaction| transaction.read_view(view_id)) .await?; - self.get_data_processor(view.data_type) + self.get_data_processor(view.data_format) } #[inline] - fn get_data_processor>( + fn get_data_processor>( &self, data_type: T, ) -> FlowyResult> { @@ -459,10 +462,10 @@ async fn handle_trash_event( let mut notify_ids = HashSet::new(); let mut views = vec![]; for identifier in identifiers.items { - let view = transaction.read_view(&identifier.id)?; - let _ = transaction.delete_view(&view.id)?; - notify_ids.insert(view.app_id.clone()); - views.push(view); + if let Ok(view_rev) = transaction.delete_view(&identifier.id) { + notify_ids.insert(view_rev.app_id.clone()); + views.push(view_rev); + } } for notify_id in notify_ids { let _ = notify_views_changed(¬ify_id, trash_can.clone(), &transaction)?; @@ -472,14 +475,12 @@ async fn handle_trash_event( .await?; for view in views { - let data_type = view.data_type.clone().into(); + let data_type = view.data_format.clone().into(); match get_data_processor(data_processors.clone(), &data_type) { Ok(processor) => { - let _ = processor.close_container(&view.id).await?; - } - Err(e) => { - tracing::error!("{}", e) + let _ = processor.close_view(&view.id).await?; } + Err(e) => tracing::error!("{}", e), } } Ok(()) @@ -491,7 +492,7 @@ async fn handle_trash_event( fn get_data_processor( data_processors: ViewDataProcessorMap, - data_type: &ViewDataTypePB, + data_type: &ViewDataFormatPB, ) -> FlowyResult> { match data_processors.get(data_type) { None => Err(FlowyError::internal().context(format!( @@ -528,16 +529,15 @@ fn notify_views_changed<'a>( trash_controller: Arc, transaction: &'a (dyn FolderPersistenceTransaction + 'a), ) -> FlowyResult<()> { - let items: Vec = read_belonging_views_on_local(belong_to_id, trash_controller.clone(), transaction)? - .into_iter() - .map(|view_rev| view_rev.into()) - .collect(); - tracing::Span::current().record("view_count", &format!("{}", items.len()).as_str()); + let mut app_rev = transaction.read_app(belong_to_id)?; + let trash_ids = trash_controller.read_trash_ids(transaction)?; + app_rev.belongings.retain(|view| !trash_ids.contains(&view.id)); + let app: AppPB = app_rev.into(); - let repeated_view = RepeatedViewPB { items }; - send_dart_notification(belong_to_id, FolderNotification::AppViewsChanged) - .payload(repeated_view) + send_dart_notification(belong_to_id, FolderNotification::AppUpdated) + .payload(app) .send(); + Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs index 2a7736e05e..06ee0e797e 100644 --- a/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/view/event_handler.rs @@ -13,7 +13,7 @@ use crate::{ errors::FlowyError, services::{TrashController, ViewController}, }; -use flowy_folder_data_model::revision::TrashRevision; +use folder_rev_model::TrashRevision; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::{convert::TryInto, sync::Arc}; @@ -121,10 +121,10 @@ pub(crate) async fn move_item_handler( #[tracing::instrument(level = "debug", skip(data, controller), err)] pub(crate) async fn duplicate_view_handler( - data: Data, + data: Data, controller: AppData>, ) -> Result<(), FlowyError> { - let view_id: ViewIdPB = data.into_inner(); - let _ = controller.duplicate_view(&view_id.value).await?; + let view: ViewPB = data.into_inner(); + let _ = controller.duplicate_view(view).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs b/frontend/rust-lib/flowy-folder/src/services/web_socket.rs index e77c82bdba..6406e80e9f 100644 --- a/frontend/rust-lib/flowy-folder/src/services/web_socket.rs +++ b/frontend/rust-lib/flowy-folder/src/services/web_socket.rs @@ -1,17 +1,13 @@ use crate::services::FOLDER_SYNC_INTERVAL_IN_MILLIS; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType}; use flowy_revision::*; -use flowy_sync::entities::revision::Revision; +use flowy_sync::client_folder::FolderPad; use flowy_sync::server_folder::FolderOperations; use flowy_sync::util::make_operations_from_revisions; -use flowy_sync::{ - client_folder::FolderPad, - entities::{ - revision::RevisionRange, - ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSDataType}, - }, -}; use lib_infra::future::{BoxResultFuture, FutureResult}; use lib_ot::core::OperationTransform; use parking_lot::RwLock; @@ -37,13 +33,13 @@ impl FolderResolveOperations { } } -pub type FolderConflictController = ConflictController; +pub type FolderConflictController = ConflictController>; #[allow(dead_code)] pub(crate) async fn make_folder_ws_manager( user_id: &str, folder_id: &str, - rev_manager: Arc, + rev_manager: Arc>>, web_socket: Arc, folder_pad: Arc>, ) -> Arc { @@ -77,12 +73,12 @@ struct FolderConflictResolver { } impl ConflictResolver for FolderConflictResolver { - fn compose_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { + fn compose_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { let operations = operations.into_inner(); let folder_pad = self.folder_pad.clone(); Box::pin(async move { let md5 = folder_pad.write().compose_remote_operations(operations)?; - Ok(md5) + Ok(md5.into()) }) } @@ -112,11 +108,11 @@ impl ConflictResolver for FolderConflictResolver { }) } - fn reset_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { + fn reset_operations(&self, operations: FolderResolveOperations) -> BoxResultFuture { let folder_pad = self.folder_pad.clone(); Box::pin(async move { let md5 = folder_pad.write().reset_folder(operations.into_inner())?; - Ok(md5) + Ok(md5.into()) }) } } diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs index 42f3f65fa1..c1ff6a5c01 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/controller.rs @@ -10,7 +10,7 @@ use crate::{ }, }; use flowy_database::kv::KV; -use flowy_folder_data_model::revision::{AppRevision, WorkspaceRevision}; +use folder_rev_model::{AppRevision, WorkspaceRevision}; use std::sync::Arc; pub struct WorkspaceController { @@ -221,11 +221,11 @@ pub async fn notify_workspace_setting_did_change( )?; let setting = match transaction.read_view(view_id) { - Ok(latest_view) => CurrentWorkspaceSettingPB { + Ok(latest_view) => WorkspaceSettingPB { workspace, latest_view: Some(latest_view.into()), }, - Err(_) => CurrentWorkspaceSettingPB { + Err(_) => WorkspaceSettingPB { workspace, latest_view: None, }, diff --git a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs index 234fa3b7de..bce1f508b5 100644 --- a/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs +++ b/frontend/rust-lib/flowy-folder/src/services/workspace/event_handler.rs @@ -1,7 +1,7 @@ use crate::entities::{ app::RepeatedAppPB, view::ViewPB, - workspace::{CurrentWorkspaceSettingPB, RepeatedWorkspacePB, WorkspaceIdPB, *}, + workspace::{RepeatedWorkspacePB, WorkspaceIdPB, WorkspaceSettingPB, *}, }; use crate::{ dart_notification::{send_dart_notification, FolderNotification}, @@ -79,7 +79,7 @@ pub(crate) async fn read_workspaces_handler( #[tracing::instrument(level = "debug", skip(folder), err)] pub async fn read_cur_workspace_handler( folder: AppData>, -) -> DataResult { +) -> DataResult { let workspace_id = get_current_workspace()?; let user_id = folder.user.user_id()?; let params = WorkspaceIdPB { @@ -101,7 +101,7 @@ pub async fn read_cur_workspace_handler( .await .unwrap_or(None) .map(|view_rev| view_rev.into()); - let setting = CurrentWorkspaceSettingPB { workspace, latest_view }; + let setting = WorkspaceSettingPB { workspace, latest_view }; let _ = read_workspaces_on_server(folder, user_id, params); data_result(setting) } @@ -119,7 +119,6 @@ fn read_workspaces_on_server( let workspace_revs = server.read_workspace(&token, params).await?; let _ = persistence .begin_transaction(|transaction| { - tracing::trace!("Save {} workspace", workspace_revs.len()); for workspace_rev in &workspace_revs { let m_workspace = workspace_rev.clone(); let app_revs = m_workspace.apps.clone(); diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs index 33564c923d..f7b93ed30a 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/folder_test.rs @@ -1,5 +1,5 @@ use crate::script::{invalid_workspace_name_test_case, FolderScript::*, FolderTest}; -use flowy_folder::entities::view::ViewDataTypePB; +use flowy_folder::entities::view::ViewDataFormatPB; use flowy_folder::entities::workspace::CreateWorkspacePayloadPB; use flowy_revision::disk::RevisionState; use flowy_test::{event_builder::*, FlowySDKTest}; @@ -133,12 +133,12 @@ async fn app_create_with_view() { CreateView { name: "View A".to_owned(), desc: "View A description".to_owned(), - data_type: ViewDataTypePB::Text, + data_type: ViewDataFormatPB::DeltaFormat, }, CreateView { name: "Grid".to_owned(), desc: "Grid description".to_owned(), - data_type: ViewDataTypePB::Database, + data_type: ViewDataFormatPB::DatabaseFormat, }, ReadApp(app.id), ]) @@ -197,12 +197,12 @@ async fn view_delete_all() { CreateView { name: "View A".to_owned(), desc: "View A description".to_owned(), - data_type: ViewDataTypePB::Text, + data_type: ViewDataFormatPB::DeltaFormat, }, CreateView { name: "Grid".to_owned(), desc: "Grid description".to_owned(), - data_type: ViewDataTypePB::Database, + data_type: ViewDataFormatPB::DatabaseFormat, }, ReadApp(app.id.clone()), ]) @@ -230,7 +230,7 @@ async fn view_delete_all_permanent() { CreateView { name: "View A".to_owned(), desc: "View A description".to_owned(), - data_type: ViewDataTypePB::Text, + data_type: ViewDataFormatPB::DeltaFormat, }, ReadApp(app.id.clone()), ]) @@ -292,53 +292,53 @@ async fn folder_sync_revision_seq() { .await; } -#[tokio::test] -async fn folder_sync_revision_with_new_app() { - let mut test = FolderTest::new().await; - let app_name = "AppFlowy contributors".to_owned(); - let app_desc = "Welcome to be a AppFlowy contributor".to_owned(); +// #[tokio::test] +// async fn folder_sync_revision_with_new_app() { +// let mut test = FolderTest::new().await; +// let app_name = "AppFlowy contributors".to_owned(); +// let app_desc = "Welcome to be a AppFlowy contributor".to_owned(); +// +// test.run_scripts(vec![ +// AssertNextSyncRevId(Some(1)), +// AssertNextSyncRevId(Some(2)), +// CreateApp { +// name: app_name.clone(), +// desc: app_desc.clone(), +// }, +// AssertCurrentRevId(3), +// AssertNextSyncRevId(Some(3)), +// AssertNextSyncRevId(None), +// ]) +// .await; +// +// let app = test.app.clone(); +// assert_eq!(app.name, app_name); +// assert_eq!(app.desc, app_desc); +// test.run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]).await; +// } - test.run_scripts(vec![ - AssertNextSyncRevId(Some(1)), - AssertNextSyncRevId(Some(2)), - CreateApp { - name: app_name.clone(), - desc: app_desc.clone(), - }, - AssertCurrentRevId(3), - AssertNextSyncRevId(Some(3)), - AssertNextSyncRevId(None), - ]) - .await; - - let app = test.app.clone(); - assert_eq!(app.name, app_name); - assert_eq!(app.desc, app_desc); - test.run_scripts(vec![ReadApp(app.id.clone()), AssertApp(app)]).await; -} - -#[tokio::test] -async fn folder_sync_revision_with_new_view() { - let mut test = FolderTest::new().await; - let view_name = "AppFlowy features".to_owned(); - let view_desc = "😁".to_owned(); - - test.run_scripts(vec![ - AssertNextSyncRevId(Some(1)), - AssertNextSyncRevId(Some(2)), - CreateView { - name: view_name.clone(), - desc: view_desc.clone(), - data_type: ViewDataTypePB::Text, - }, - AssertCurrentRevId(3), - AssertNextSyncRevId(Some(3)), - AssertNextSyncRevId(None), - ]) - .await; - - let view = test.view.clone(); - assert_eq!(view.name, view_name); - test.run_scripts(vec![ReadView(view.id.clone()), AssertView(view)]) - .await; -} +// #[tokio::test] +// async fn folder_sync_revision_with_new_view() { +// let mut test = FolderTest::new().await; +// let view_name = "AppFlowy features".to_owned(); +// let view_desc = "😁".to_owned(); +// +// test.run_scripts(vec![ +// AssertNextSyncRevId(Some(1)), +// AssertNextSyncRevId(Some(2)), +// CreateView { +// name: view_name.clone(), +// desc: view_desc.clone(), +// data_type: ViewDataFormatPB::DeltaFormat, +// }, +// AssertCurrentRevId(3), +// AssertNextSyncRevId(Some(3)), +// AssertNextSyncRevId(None), +// ]) +// .await; +// +// let view = test.view.clone(); +// assert_eq!(view.name, view_name); +// test.run_scripts(vec![ReadView(view.id.clone()), AssertView(view)]) +// .await; +// } diff --git a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs index 005b685a94..26fc2829a6 100644 --- a/frontend/rust-lib/flowy-folder/tests/workspace/script.rs +++ b/frontend/rust-lib/flowy-folder/tests/workspace/script.rs @@ -10,15 +10,15 @@ use flowy_folder::entities::{ use flowy_folder::entities::{ app::{AppPB, RepeatedAppPB}, trash::TrashPB, - view::{RepeatedViewPB, ViewDataTypePB, ViewPB}, + view::{RepeatedViewPB, ViewDataFormatPB, ViewPB}, workspace::WorkspacePB, }; use flowy_folder::event_map::FolderEvent::*; use flowy_folder::{errors::ErrorCode, services::folder_editor::FolderEditor}; +use flowy_http_model::document::DocumentPayloadPB; use flowy_revision::disk::RevisionState; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; -use flowy_sync::entities::document::DocumentPayloadPB; use flowy_test::{event_builder::*, FlowySDKTest}; use std::{sync::Arc, time::Duration}; use tokio::time::sleep; @@ -52,7 +52,7 @@ pub enum FolderScript { CreateView { name: String, desc: String, - data_type: ViewDataTypePB, + data_type: ViewDataFormatPB, }, AssertView(ViewPB), ReadView(String), @@ -70,6 +70,7 @@ pub enum FolderScript { DeleteAllTrash, // Sync + #[allow(dead_code)] AssertCurrentRevId(i64), AssertNextSyncRevId(Option), AssertRevisionState { @@ -99,7 +100,7 @@ impl FolderTest { &app.id, "Folder View", "Folder test view", - ViewDataTypePB::Text, + ViewDataFormatPB::DeltaFormat, ViewLayoutTypePB::Document, ) .await; @@ -182,8 +183,9 @@ impl FolderTest { FolderScript::CreateView { name, desc, data_type } => { let layout = match data_type { - ViewDataTypePB::Text => ViewLayoutTypePB::Document, - ViewDataTypePB::Database => ViewLayoutTypePB::Grid, + ViewDataFormatPB::DeltaFormat => ViewLayoutTypePB::Document, + ViewDataFormatPB::TreeFormat => ViewLayoutTypePB::Document, + ViewDataFormatPB::DatabaseFormat => ViewLayoutTypePB::Grid, }; let view = create_view(sdk, &self.app.id, &name, &desc, data_type, layout).await; self.view = view; @@ -357,7 +359,7 @@ pub async fn create_view( app_id: &str, name: &str, desc: &str, - data_type: ViewDataTypePB, + data_type: ViewDataFormatPB, layout: ViewLayoutTypePB, ) -> ViewPB { let request = CreateViewPayloadPB { @@ -365,7 +367,7 @@ pub async fn create_view( name: name.to_string(), desc: desc.to_string(), thumbnail: None, - data_type, + data_format: data_type, layout, view_content_data: vec![], }; diff --git a/frontend/rust-lib/flowy-grid/Cargo.toml b/frontend/rust-lib/flowy-grid/Cargo.toml index b52bb4582d..f4325b8bcc 100644 --- a/frontend/rust-lib/flowy-grid/Cargo.toml +++ b/frontend/rust-lib/flowy-grid/Cargo.toml @@ -9,13 +9,16 @@ edition = "2021" lib-dispatch = { path = "../lib-dispatch" } dart-notify = { path = "../dart-notify" } flowy-revision = { path = "../flowy-revision" } +flowy-task= { path = "../flowy-task" } flowy-error = { path = "../flowy-error", features = ["db"]} flowy-derive = { path = "../../../shared-lib/flowy-derive" } lib-ot = { path = "../../../shared-lib/lib-ot" } lib-infra = { path = "../../../shared-lib/lib-infra" } -flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" } +grid-rev-model = { path = "../../../shared-lib/grid-rev-model" } flowy-sync = { path = "../../../shared-lib/flowy-sync" } +flowy-http-model = { path = "../../../shared-lib/flowy-http-model" } flowy-database = { path = "../flowy-database" } +anyhow = "1.0" strum = "0.21" strum_macros = "0.21" @@ -41,13 +44,14 @@ url = { version = "2"} futures = "0.3.15" atomic_refcell = "0.1.8" crossbeam-utils = "0.8.7" +async-stream = "0.3.2" [dev-dependencies] flowy-test = { path = "../flowy-test" } flowy-grid = { path = "../flowy-grid", features = ["flowy_unit_test"]} [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "proto_gen"] } +lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] } [features] diff --git a/frontend/rust-lib/flowy-grid/src/dart_notification.rs b/frontend/rust-lib/flowy-grid/src/dart_notification.rs index 6e47dfceb9..bfa1636002 100644 --- a/frontend/rust-lib/flowy-grid/src/dart_notification.rs +++ b/frontend/rust-lib/flowy-grid/src/dart_notification.rs @@ -3,7 +3,7 @@ use flowy_derive::ProtoBuf_Enum; const OBSERVABLE_CATEGORY: &str = "Grid"; #[derive(ProtoBuf_Enum, Debug)] -pub enum GridNotification { +pub enum GridDartNotification { Unknown = 0, DidCreateBlock = 11, DidUpdateGridBlock = 20, @@ -14,22 +14,23 @@ pub enum GridNotification { DidUpdateGroupView = 60, DidUpdateGroup = 61, DidGroupByNewField = 62, + DidUpdateFilter = 63, DidUpdateGridSetting = 70, } -impl std::default::Default for GridNotification { +impl std::default::Default for GridDartNotification { fn default() -> Self { - GridNotification::Unknown + GridDartNotification::Unknown } } -impl std::convert::From for i32 { - fn from(notification: GridNotification) -> Self { +impl std::convert::From for i32 { + fn from(notification: GridDartNotification) -> Self { notification as i32 } } #[tracing::instrument(level = "trace")] -pub fn send_dart_notification(id: &str, ty: GridNotification) -> DartNotifyBuilder { +pub fn send_dart_notification(id: &str, ty: GridDartNotification) -> DartNotifyBuilder { DartNotifyBuilder::new(id, ty, OBSERVABLE_CATEGORY) } diff --git a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs index 6e271a0fb2..05c9f28636 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/block_entities.rs @@ -1,7 +1,7 @@ +use crate::entities::parser::NotEmptyStr; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::RowRevision; +use grid_rev_model::RowRevision; use std::sync::Arc; /// [BlockPB] contains list of row ids. The rows here does not contain any data, just the id @@ -152,7 +152,7 @@ impl std::convert::From<&RowRevision> for InsertedRowPB { } } -#[derive(Debug, Default, ProtoBuf)] +#[derive(Debug, Default, Clone, ProtoBuf)] pub struct GridBlockChangesetPB { #[pb(index = 1)] pub block_id: String, @@ -170,7 +170,7 @@ pub struct GridBlockChangesetPB { pub visible_rows: Vec, #[pb(index = 6)] - pub hide_rows: Vec, + pub invisible_rows: Vec, } impl GridBlockChangesetPB { pub fn insert(block_id: String, inserted_rows: Vec) -> Self { diff --git a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs index 48886f1918..4b596719fe 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs @@ -1,8 +1,8 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::FieldType; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{CellRevision, RowChangeset}; +use grid_rev_model::{CellRevision, RowChangeset}; use std::collections::HashMap; #[derive(ProtoBuf, Default)] @@ -39,7 +39,7 @@ impl TryInto for CreateSelectOptionPayloadPB { } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct GridCellIdPB { +pub struct CellPathPB { #[pb(index = 1)] pub grid_id: String, @@ -50,20 +50,20 @@ pub struct GridCellIdPB { pub row_id: String, } -pub struct GridCellIdParams { +pub struct CellPathParams { pub grid_id: String, pub field_id: String, pub row_id: String, } -impl TryInto for GridCellIdPB { +impl TryInto for CellPathPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; let row_id = NotEmptyStr::parse(self.row_id).map_err(|_| ErrorCode::RowIdIsEmpty)?; - Ok(GridCellIdParams { + Ok(CellPathParams { grid_id: grid_id.0, field_id: field_id.0, row_id: row_id.0, @@ -71,7 +71,7 @@ impl TryInto for GridCellIdPB { } } #[derive(Debug, Default, ProtoBuf)] -pub struct GridCellPB { +pub struct CellPB { #[pb(index = 1)] pub field_id: String, @@ -83,7 +83,7 @@ pub struct GridCellPB { pub field_type: Option, } -impl GridCellPB { +impl CellPB { pub fn new(field_id: &str, field_type: FieldType, data: Vec) -> Self { Self { field_id: field_id.to_owned(), @@ -104,11 +104,11 @@ impl GridCellPB { #[derive(Debug, Default, ProtoBuf)] pub struct RepeatedCellPB { #[pb(index = 1)] - pub items: Vec, + pub items: Vec, } impl std::ops::Deref for RepeatedCellPB { - type Target = Vec; + type Target = Vec; fn deref(&self) -> &Self::Target { &self.items } @@ -120,8 +120,8 @@ impl std::ops::DerefMut for RepeatedCellPB { } } -impl std::convert::From> for RepeatedCellPB { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedCellPB { + fn from(items: Vec) -> Self { Self { items } } } diff --git a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs index 6675fcf034..a46d1f99f8 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs @@ -1,10 +1,10 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision}; +use grid_rev_model::{FieldRevision, FieldTypeRevision}; use serde_repr::*; use std::sync::Arc; +use crate::entities::parser::NotEmptyStr; use strum_macros::{Display, EnumCount as EnumCountMacro, EnumIter, EnumString}; /// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc. @@ -84,7 +84,7 @@ impl std::convert::From<&Arc> for FieldIdPB { } } #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldChangesetPB { +pub struct GridFieldChangesetPB { #[pb(index = 1)] pub grid_id: String, @@ -98,7 +98,7 @@ pub struct FieldChangesetPB { pub updated_fields: Vec, } -impl FieldChangesetPB { +impl GridFieldChangesetPB { pub fn insert(grid_id: &str, inserted_fields: Vec) -> Self { Self { grid_id: grid_id.to_owned(), @@ -145,18 +145,6 @@ impl IndexFieldPB { } } -#[derive(Debug, Default, ProtoBuf)] -pub struct GetEditFieldContextPayloadPB { - #[pb(index = 1)] - pub grid_id: String, - - #[pb(index = 2, one_of)] - pub field_id: Option, - - #[pb(index = 3)] - pub field_type: FieldType, -} - #[derive(Debug, Default, ProtoBuf)] pub struct CreateFieldPayloadPB { #[pb(index = 1)] @@ -190,7 +178,7 @@ impl TryInto for CreateFieldPayloadPB { } #[derive(Debug, Default, ProtoBuf)] -pub struct EditFieldPayloadPB { +pub struct EditFieldChangesetPB { #[pb(index = 1)] pub grid_id: String, @@ -210,7 +198,7 @@ pub struct EditFieldParams { pub field_type: FieldType, } -impl TryInto for EditFieldPayloadPB { +impl TryInto for EditFieldChangesetPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -225,7 +213,7 @@ impl TryInto for EditFieldPayloadPB { } #[derive(Debug, Default, ProtoBuf)] -pub struct FieldTypeOptionIdPB { +pub struct TypeOptionPathPB { #[pb(index = 1)] pub grid_id: String, @@ -236,19 +224,19 @@ pub struct FieldTypeOptionIdPB { pub field_type: FieldType, } -pub struct FieldTypeOptionIdParams { +pub struct TypeOptionPathParams { pub grid_id: String, pub field_id: String, pub field_type: FieldType, } -impl TryInto for FieldTypeOptionIdPB { +impl TryInto for TypeOptionPathPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(FieldTypeOptionIdParams { + Ok(TypeOptionPathParams { grid_id: grid_id.0, field_id: field_id.0, field_type: self.field_type, @@ -257,7 +245,7 @@ impl TryInto for FieldTypeOptionIdPB { } #[derive(Debug, Default, ProtoBuf)] -pub struct FieldTypeOptionDataPB { +pub struct TypeOptionPB { #[pb(index = 1)] pub grid_id: String, @@ -320,35 +308,35 @@ impl std::convert::From for RepeatedFieldIdPB { } } -/// [UpdateFieldTypeOptionPayloadPB] is used to update the type-option data. +/// [TypeOptionChangesetPB] is used to update the type-option data. #[derive(ProtoBuf, Default)] -pub struct UpdateFieldTypeOptionPayloadPB { +pub struct TypeOptionChangesetPB { #[pb(index = 1)] pub grid_id: String, #[pb(index = 2)] pub field_id: String, - /// Check out [FieldTypeOptionDataPB] for more details. + /// Check out [TypeOptionPB] for more details. #[pb(index = 3)] pub type_option_data: Vec, } #[derive(Clone)] -pub struct UpdateFieldTypeOptionParams { +pub struct TypeOptionChangesetParams { pub grid_id: String, pub field_id: String, pub type_option_data: Vec, } -impl TryInto for UpdateFieldTypeOptionPayloadPB { +impl TryInto for TypeOptionChangesetPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; let _ = NotEmptyStr::parse(self.field_id.clone()).map_err(|_| ErrorCode::FieldIdIsEmpty)?; - Ok(UpdateFieldTypeOptionParams { + Ok(TypeOptionChangesetParams { grid_id: grid_id.0, field_id: self.field_id, type_option_data: self.type_option_data, @@ -357,7 +345,7 @@ impl TryInto for UpdateFieldTypeOptionPayloadPB { } #[derive(ProtoBuf, Default)] -pub struct QueryFieldPayloadPB { +pub struct GetFieldPayloadPB { #[pb(index = 1)] pub grid_id: String, @@ -365,31 +353,31 @@ pub struct QueryFieldPayloadPB { pub field_ids: RepeatedFieldIdPB, } -pub struct QueryFieldParams { +pub struct GetFieldParams { pub grid_id: String, pub field_ids: RepeatedFieldIdPB, } -impl TryInto for QueryFieldPayloadPB { +impl TryInto for GetFieldPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let grid_id = NotEmptyStr::parse(self.grid_id).map_err(|_| ErrorCode::GridIdIsEmpty)?; - Ok(QueryFieldParams { + Ok(GetFieldParams { grid_id: grid_id.0, field_ids: self.field_ids, }) } } -/// [FieldChangesetPayloadPB] is used to modify the corresponding field. It defines which properties of +/// [FieldChangesetPB] is used to modify the corresponding field. It defines which properties of /// the field can be modified. /// /// Pass in None if you don't want to modify a property /// Pass in Some(Value) if you want to modify a property /// #[derive(Debug, Clone, Default, ProtoBuf)] -pub struct FieldChangesetPayloadPB { +pub struct FieldChangesetPB { #[pb(index = 1)] pub field_id: String, @@ -418,7 +406,7 @@ pub struct FieldChangesetPayloadPB { pub type_option_data: Option>, } -impl TryInto for FieldChangesetPayloadPB { +impl TryInto for FieldChangesetPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -465,18 +453,6 @@ pub struct FieldChangesetParams { pub type_option_data: Option>, } - -impl FieldChangesetParams { - pub fn has_changes(&self) -> bool { - self.name.is_some() - || self.desc.is_some() - || self.field_type.is_some() - || self.frozen.is_some() - || self.type_option_data.is_some() - || self.frozen.is_some() - || self.width.is_some() - } -} /// Certain field types have user-defined options such as color, date format, number format, /// or a list of values for a multi-select list. These options are defined within a specialization /// of the FieldTypeOption class. @@ -603,6 +579,7 @@ impl std::convert::From<&FieldTypeRevision> for FieldType { FieldType::from(*ty) } } + impl std::convert::From for FieldType { fn from(ty: FieldTypeRevision) -> Self { match ty { diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs index 45a64af245..6476926b39 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs @@ -1,49 +1,48 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::revision::FilterConfigurationRevision; -use std::sync::Arc; +use grid_rev_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct CheckboxFilterConfigurationPB { +pub struct CheckboxFilterPB { #[pb(index = 1)] - pub condition: CheckboxCondition, + pub condition: CheckboxFilterCondition, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] -pub enum CheckboxCondition { +pub enum CheckboxFilterCondition { IsChecked = 0, IsUnChecked = 1, } -impl std::convert::From for i32 { - fn from(value: CheckboxCondition) -> Self { - value as i32 +impl std::convert::From for u32 { + fn from(value: CheckboxFilterCondition) -> Self { + value as u32 } } -impl std::default::Default for CheckboxCondition { +impl std::default::Default for CheckboxFilterCondition { fn default() -> Self { - CheckboxCondition::IsChecked + CheckboxFilterCondition::IsChecked } } -impl std::convert::TryFrom for CheckboxCondition { +impl std::convert::TryFrom for CheckboxFilterCondition { type Error = ErrorCode; fn try_from(value: u8) -> Result { match value { - 0 => Ok(CheckboxCondition::IsChecked), - 1 => Ok(CheckboxCondition::IsUnChecked), + 0 => Ok(CheckboxFilterCondition::IsChecked), + 1 => Ok(CheckboxFilterCondition::IsUnChecked), _ => Err(ErrorCode::InvalidData), } } } -impl std::convert::From> for CheckboxFilterConfigurationPB { - fn from(rev: Arc) -> Self { - CheckboxFilterConfigurationPB { - condition: CheckboxCondition::try_from(rev.condition).unwrap_or(CheckboxCondition::IsChecked), +impl std::convert::From<&FilterRevision> for CheckboxFilterPB { + fn from(rev: &FilterRevision) -> Self { + CheckboxFilterPB { + condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked), } } } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs index 72be45a655..84fa43471f 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs @@ -1,14 +1,11 @@ -use crate::entities::FieldType; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::FilterConfigurationRevision; +use grid_rev_model::FilterRevision; use serde::{Deserialize, Serialize}; use std::str::FromStr; -use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct DateFilterConfigurationPB { +pub struct DateFilterPB { #[pb(index = 1)] pub condition: DateFilterCondition, @@ -17,68 +14,25 @@ pub struct DateFilterConfigurationPB { #[pb(index = 3, one_of)] pub end: Option, -} - -#[derive(ProtoBuf, Default, Clone, Debug)] -pub struct CreateGridDateFilterPayload { - #[pb(index = 1)] - pub field_id: String, - - #[pb(index = 2)] - pub field_type: FieldType, - - #[pb(index = 3)] - pub condition: DateFilterCondition, #[pb(index = 4, one_of)] + pub timestamp: Option, +} + +#[derive(Deserialize, Serialize, Default, Clone, Debug)] +pub struct DateFilterContent { pub start: Option, - - #[pb(index = 5, one_of)] pub end: Option, + pub timestamp: Option, } -pub struct CreateGridDateFilterParams { - pub field_id: String, - - pub field_type: FieldType, - - pub condition: DateFilterCondition, - - pub start: Option, - - pub end: Option, -} - -impl TryInto for CreateGridDateFilterPayload { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - Ok(CreateGridDateFilterParams { - field_id, - condition: self.condition, - start: self.start, - field_type: self.field_type, - end: self.end, - }) - } -} - -#[derive(Serialize, Deserialize, Default)] -struct DateRange { - start: Option, - end: Option, -} - -impl ToString for DateRange { +impl ToString for DateFilterContent { fn to_string(&self) -> String { - serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + serde_json::to_string(self).unwrap() } } -impl FromStr for DateRange { +impl FromStr for DateFilterContent { type Err = serde_json::Error; fn from_str(s: &str) -> Result { @@ -96,8 +50,14 @@ pub enum DateFilterCondition { DateOnOrAfter = 4, DateWithIn = 5, DateIsEmpty = 6, + DateIsNotEmpty = 7, } +impl std::convert::From for u32 { + fn from(value: DateFilterCondition) -> Self { + value as u32 + } +} impl std::default::Default for DateFilterCondition { fn default() -> Self { DateFilterCondition::DateIs @@ -120,21 +80,18 @@ impl std::convert::TryFrom for DateFilterCondition { } } } -impl std::convert::From> for DateFilterConfigurationPB { - fn from(rev: Arc) -> Self { +impl std::convert::From<&FilterRevision> for DateFilterPB { + fn from(rev: &FilterRevision) -> Self { let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs); - let mut filter = DateFilterConfigurationPB { + let mut filter = DateFilterPB { condition, ..Default::default() }; - if let Some(range) = rev - .content - .as_ref() - .and_then(|content| DateRange::from_str(content).ok()) - { - filter.start = range.start; - filter.end = range.end; + if let Ok(content) = DateFilterContent::from_str(&rev.content) { + filter.start = content.start; + filter.end = content.end; + filter.timestamp = content.timestamp; }; filter diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/filter_changeset.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/filter_changeset.rs new file mode 100644 index 0000000000..25c5b6b2ce --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/filter_changeset.rs @@ -0,0 +1,31 @@ +use crate::entities::FilterPB; +use flowy_derive::ProtoBuf; + +#[derive(Debug, Default, ProtoBuf)] +pub struct FilterChangesetNotificationPB { + #[pb(index = 1)] + pub view_id: String, + + #[pb(index = 2)] + pub insert_filters: Vec, + + #[pb(index = 3)] + pub delete_filters: Vec, +} + +impl FilterChangesetNotificationPB { + pub fn from_insert(view_id: &str, filters: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_filters: filters, + delete_filters: Default::default(), + } + } + pub fn from_delete(view_id: &str, filters: Vec) -> Self { + Self { + view_id: view_id.to_string(), + insert_filters: Default::default(), + delete_filters: filters, + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs index bb033f5600..435dac56f2 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs @@ -1,5 +1,6 @@ mod checkbox_filter; mod date_filter; +mod filter_changeset; mod number_filter; mod select_option_filter; mod text_filter; @@ -7,6 +8,7 @@ mod util; pub use checkbox_filter::*; pub use date_filter::*; +pub use filter_changeset::*; pub use number_filter::*; pub use select_option_filter::*; pub use text_filter::*; diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs index 68c7474b8f..0eed1a9e1c 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs @@ -1,16 +1,14 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::revision::FilterConfigurationRevision; - -use std::sync::Arc; +use grid_rev_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct NumberFilterConfigurationPB { +pub struct NumberFilterPB { #[pb(index = 1)] pub condition: NumberFilterCondition, - #[pb(index = 2, one_of)] - pub content: Option, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] @@ -32,9 +30,9 @@ impl std::default::Default for NumberFilterCondition { } } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: NumberFilterCondition) -> Self { - value as i32 + value as u32 } } impl std::convert::TryFrom for NumberFilterCondition { @@ -55,9 +53,9 @@ impl std::convert::TryFrom for NumberFilterCondition { } } -impl std::convert::From> for NumberFilterConfigurationPB { - fn from(rev: Arc) -> Self { - NumberFilterConfigurationPB { +impl std::convert::From<&FilterRevision> for NumberFilterPB { + fn from(rev: &FilterRevision) -> Self { + NumberFilterPB { condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), content: rev.content.clone(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs index 47e07c0b73..d73ef933a1 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs @@ -1,17 +1,17 @@ use crate::services::field::SelectOptionIds; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::revision::FilterConfigurationRevision; -use std::sync::Arc; +use grid_rev_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct SelectOptionFilterConfigurationPB { +pub struct SelectOptionFilterPB { #[pb(index = 1)] pub condition: SelectOptionCondition, #[pb(index = 2)] pub option_ids: Vec, } + #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] pub enum SelectOptionCondition { @@ -21,9 +21,9 @@ pub enum SelectOptionCondition { OptionIsNotEmpty = 3, } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: SelectOptionCondition) -> Self { - value as i32 + value as u32 } } @@ -47,10 +47,10 @@ impl std::convert::TryFrom for SelectOptionCondition { } } -impl std::convert::From> for SelectOptionFilterConfigurationPB { - fn from(rev: Arc) -> Self { +impl std::convert::From<&FilterRevision> for SelectOptionFilterPB { + fn from(rev: &FilterRevision) -> Self { let ids = SelectOptionIds::from(rev.content.clone()); - SelectOptionFilterConfigurationPB { + SelectOptionFilterPB { condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), option_ids: ids.into_inner(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs index 802941516d..57f1fcfdf5 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs @@ -1,15 +1,14 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::revision::FilterConfigurationRevision; -use std::sync::Arc; +use grid_rev_model::FilterRevision; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct TextFilterConfigurationPB { +pub struct TextFilterPB { #[pb(index = 1)] pub condition: TextFilterCondition, - #[pb(index = 2, one_of)] - pub content: Option, + #[pb(index = 2)] + pub content: String, } #[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] @@ -25,9 +24,9 @@ pub enum TextFilterCondition { TextIsNotEmpty = 7, } -impl std::convert::From for i32 { +impl std::convert::From for u32 { fn from(value: TextFilterCondition) -> Self { - value as i32 + value as u32 } } @@ -36,6 +35,7 @@ impl std::default::Default for TextFilterCondition { TextFilterCondition::Is } } + impl std::convert::TryFrom for TextFilterCondition { type Error = ErrorCode; @@ -54,9 +54,9 @@ impl std::convert::TryFrom for TextFilterCondition { } } -impl std::convert::From> for TextFilterConfigurationPB { - fn from(rev: Arc) -> Self { - TextFilterConfigurationPB { +impl std::convert::From<&FilterRevision> for TextFilterPB { + fn from(rev: &FilterRevision) -> Self { + TextFilterPB { condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), content: rev.content.clone(), } diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs index c6737e8e76..0cfb5c5e60 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs @@ -1,42 +1,64 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::{ - CheckboxCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, - TextFilterCondition, + CheckboxFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB, }; +use crate::services::field::SelectOptionIds; +use crate::services::filter::FilterType; +use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision}; +use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision}; use std::convert::TryInto; use std::sync::Arc; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct GridFilterConfigurationPB { +pub struct FilterPB { #[pb(index = 1)] pub id: String, + + #[pb(index = 2)] + pub ty: FieldType, + + #[pb(index = 3)] + pub data: Vec, } -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] -pub struct RepeatedGridFilterConfigurationPB { - #[pb(index = 1)] - pub items: Vec, -} - -impl std::convert::From<&FilterConfigurationRevision> for GridFilterConfigurationPB { - fn from(rev: &FilterConfigurationRevision) -> Self { - Self { id: rev.id.clone() } +impl std::convert::From<&FilterRevision> for FilterPB { + fn from(rev: &FilterRevision) -> Self { + let field_type: FieldType = rev.field_type_rev.into(); + let bytes: Bytes = match field_type { + FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(), + FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(), + FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(), + FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), + FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(), + FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(), + FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(), + }; + Self { + id: rev.id.clone(), + ty: rev.field_type_rev.into(), + data: bytes.to_vec(), + } } } -impl std::convert::From>> for RepeatedGridFilterConfigurationPB { - fn from(revs: Vec>) -> Self { - RepeatedGridFilterConfigurationPB { +#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +pub struct RepeatedFilterPB { + #[pb(index = 1)] + pub items: Vec, +} + +impl std::convert::From>> for RepeatedFilterPB { + fn from(revs: Vec>) -> Self { + RepeatedFilterPB { items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), } } } -impl std::convert::From> for RepeatedGridFilterConfigurationPB { - fn from(items: Vec) -> Self { +impl std::convert::From> for RepeatedFilterPB { + fn from(items: Vec) -> Self { Self { items } } } @@ -47,10 +69,10 @@ pub struct DeleteFilterPayloadPB { pub field_id: String, #[pb(index = 2)] - pub filter_id: String, + pub field_type: FieldType, #[pb(index = 3)] - pub field_type: FieldType, + pub filter_id: String, } impl TryInto for DeleteFilterPayloadPB { @@ -60,25 +82,28 @@ impl TryInto for DeleteFilterPayloadPB { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; + let filter_id = NotEmptyStr::parse(self.filter_id) .map_err(|_| ErrorCode::UnexpectedEmptyString)? .0; - Ok(DeleteFilterParams { + + let filter_type = FilterType { field_id, - filter_id, - field_type_rev: self.field_type.into(), - }) + field_type: self.field_type, + }; + + Ok(DeleteFilterParams { filter_id, filter_type }) } } +#[derive(Debug)] pub struct DeleteFilterParams { - pub field_id: String, + pub filter_type: FilterType, pub filter_id: String, - pub field_type_rev: FieldTypeRevision, } #[derive(ProtoBuf, Debug, Default, Clone)] -pub struct InsertFilterPayloadPB { +pub struct CreateFilterPayloadPB { #[pb(index = 1)] pub field_id: String, @@ -86,62 +111,77 @@ pub struct InsertFilterPayloadPB { pub field_type: FieldType, #[pb(index = 3)] - pub condition: i32, - - #[pb(index = 4, one_of)] - pub content: Option, + pub data: Vec, } -impl InsertFilterPayloadPB { +impl CreateFilterPayloadPB { #[allow(dead_code)] - pub fn new>(field_rev: &FieldRevision, condition: T, content: Option) -> Self { + pub fn new>(field_rev: &FieldRevision, data: T) -> Self { + let data = data.try_into().unwrap_or_else(|_| Bytes::new()); Self { field_id: field_rev.id.clone(), field_type: field_rev.ty.into(), - condition: condition.into(), - content, + data: data.to_vec(), } } } -impl TryInto for InsertFilterPayloadPB { +impl TryInto for CreateFilterPayloadPB { type Error = ErrorCode; - fn try_into(self) -> Result { + fn try_into(self) -> Result { let field_id = NotEmptyStr::parse(self.field_id) .map_err(|_| ErrorCode::FieldIdIsEmpty)? .0; - let condition = self.condition as u8; + let condition; + let mut content = "".to_string(); + let bytes: &[u8] = self.data.as_ref(); + match self.field_type { FieldType::RichText | FieldType::URL => { - let _ = TextFilterCondition::try_from(condition)?; + let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = filter.content; } FieldType::Checkbox => { - let _ = CheckboxCondition::try_from(condition)?; + let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; } FieldType::Number => { - let _ = NumberFilterCondition::try_from(condition)?; + let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = filter.content; } FieldType::DateTime => { - let _ = DateFilterCondition::try_from(condition)?; + let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = DateFilterContent { + start: filter.start, + end: filter.end, + timestamp: filter.timestamp, + } + .to_string(); } FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = SelectOptionCondition::try_from(condition)?; + let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; + condition = filter.condition as u8; + content = SelectOptionIds::from(filter.option_ids).to_string(); } } - Ok(InsertFilterParams { + Ok(CreateFilterParams { field_id, field_type_rev: self.field_type.into(), condition, - content: self.content, + content, }) } } -pub struct InsertFilterParams { +#[derive(Debug)] +pub struct CreateFilterParams { pub field_id: String, pub field_type_rev: FieldTypeRevision, pub condition: u8, - pub content: Option, + pub content: String, } diff --git a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs index c012376f55..f5af76b331 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/grid_entities.rs @@ -1,7 +1,7 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::{BlockPB, FieldIdPB}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; /// [GridPB] describes how many fields and blocks the grid has #[derive(Debug, Clone, Default, ProtoBuf)] diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs index 19f5b27a9b..c3b08af328 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/configuration.rs @@ -1,5 +1,5 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_grid_data_model::revision::{GroupRevision, SelectOptionGroupConfigurationRevision}; +use grid_rev_model::{GroupRevision, SelectOptionGroupConfigurationRevision}; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct UrlGroupConfigurationPB { diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs index d2c6eb0efb..ea2639ea05 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs @@ -1,9 +1,9 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::{CreateRowParams, FieldType, GridLayout, RowPB}; use crate::services::group::Group; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldTypeRevision, GroupConfigurationRevision}; +use grid_rev_model::{FieldTypeRevision, GroupConfigurationRevision}; use std::convert::TryInto; use std::sync::Arc; diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs index b6c9aafaff..b0f6056ad0 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs @@ -1,11 +1,11 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::{GroupPB, InsertedRowPB, RowPB}; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; use std::fmt::Formatter; #[derive(Debug, Default, ProtoBuf)] -pub struct GroupChangesetPB { +pub struct GroupRowsNotificationPB { #[pb(index = 1)] pub group_id: String, @@ -22,7 +22,7 @@ pub struct GroupChangesetPB { pub updated_rows: Vec, } -impl std::fmt::Display for GroupChangesetPB { +impl std::fmt::Display for GroupRowsNotificationPB { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { for inserted_row in &self.inserted_rows { let _ = f.write_fmt(format_args!( @@ -39,7 +39,7 @@ impl std::fmt::Display for GroupChangesetPB { } } -impl GroupChangesetPB { +impl GroupRowsNotificationPB { pub fn is_empty(&self) -> bool { self.group_name.is_none() && self.inserted_rows.is_empty() diff --git a/frontend/rust-lib/flowy-grid/src/entities/mod.rs b/frontend/rust-lib/flowy-grid/src/entities/mod.rs index eb11d982a4..44daf6bedc 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/mod.rs @@ -1,11 +1,12 @@ -mod block_entities; +pub mod block_entities; mod cell_entities; mod field_entities; -mod filter_entities; +pub mod filter_entities; mod grid_entities; mod group_entities; +pub mod parser; mod row_entities; -mod setting_entities; +pub mod setting_entities; pub use block_entities::*; pub use cell_entities::*; diff --git a/shared-lib/flowy-grid-data-model/src/parser/str_parser.rs b/frontend/rust-lib/flowy-grid/src/entities/parser.rs similarity index 100% rename from shared-lib/flowy-grid-data-model/src/parser/str_parser.rs rename to frontend/rust-lib/flowy-grid/src/entities/parser.rs diff --git a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs index 398351371c..af6a9a54f5 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/row_entities.rs @@ -1,7 +1,7 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::GridLayout; use flowy_derive::ProtoBuf; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; #[derive(Debug, Default, Clone, ProtoBuf)] pub struct RowIdPB { diff --git a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs index bbf5af831f..fbeb2117d6 100644 --- a/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs @@ -1,12 +1,11 @@ +use crate::entities::parser::NotEmptyStr; use crate::entities::{ - DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, DeleteGroupPayloadPB, InsertFilterParams, - InsertFilterPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB, - RepeatedGridGroupConfigurationPB, + CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, + DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGridGroupConfigurationPB, }; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::LayoutRevision; +use grid_rev_model::LayoutRevision; use std::convert::TryInto; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -21,7 +20,7 @@ pub struct GridSettingPB { pub layout_type: GridLayout, #[pb(index = 3)] - pub filter_configurations: RepeatedGridFilterConfigurationPB, + pub filter_configurations: RepeatedFilterPB, #[pb(index = 4)] pub group_configurations: RepeatedGridGroupConfigurationPB, @@ -76,7 +75,7 @@ impl std::convert::From for LayoutRevision { } #[derive(Default, ProtoBuf)] -pub struct GridSettingChangesetPayloadPB { +pub struct GridSettingChangesetPB { #[pb(index = 1)] pub grid_id: String, @@ -84,7 +83,7 @@ pub struct GridSettingChangesetPayloadPB { pub layout_type: GridLayout, #[pb(index = 3, one_of)] - pub insert_filter: Option, + pub insert_filter: Option, #[pb(index = 4, one_of)] pub delete_filter: Option, @@ -96,7 +95,7 @@ pub struct GridSettingChangesetPayloadPB { pub delete_group: Option, } -impl TryInto for GridSettingChangesetPayloadPB { +impl TryInto for GridSettingChangesetPB { type Error = ErrorCode; fn try_into(self) -> Result { @@ -138,7 +137,7 @@ impl TryInto for GridSettingChangesetPayloadPB { pub struct GridSettingChangesetParams { pub grid_id: String, pub layout_type: LayoutRevision, - pub insert_filter: Option, + pub insert_filter: Option, pub delete_filter: Option, pub insert_group: Option, pub delete_group: Option, diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index 8d2ac8d9f0..0820a54d56 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -1,15 +1,15 @@ use crate::entities::*; use crate::manager::GridManager; -use crate::services::cell::AnyCellData; +use crate::services::cell::TypeCellData; use crate::services::field::{ default_type_option_builder_from_type, select_type_option_from_field_rev, type_option_builder_from_json_str, - DateChangesetParams, DateChangesetPayloadPB, SelectOptionCellChangeset, SelectOptionCellChangesetParams, - SelectOptionCellChangesetPayloadPB, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPayloadPB, + DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset, SelectOptionCellChangesetPB, + SelectOptionCellChangesetParams, SelectOptionCellDataPB, SelectOptionChangeset, SelectOptionChangesetPB, SelectOptionPB, }; -use crate::services::row::make_row_from_row_rev; +use crate::services::row::{make_block_pbs, make_row_from_row_rev}; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::FieldRevision; +use grid_rev_model::FieldRevision; use lib_dispatch::prelude::{data_result, AppData, Data, DataResult}; use std::sync::Arc; @@ -20,7 +20,7 @@ pub(crate) async fn get_grid_handler( ) -> DataResult { let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; - let grid = editor.get_grid_data().await?; + let grid = editor.get_grid().await?; data_result(grid) } @@ -31,18 +31,18 @@ pub(crate) async fn get_grid_setting_handler( ) -> DataResult { let grid_id: GridIdPB = data.into_inner(); let editor = manager.open_grid(grid_id).await?; - let grid_setting = editor.get_grid_setting().await?; + let grid_setting = editor.get_setting().await?; data_result(grid_setting) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_grid_setting_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: GridSettingChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; if let Some(insert_params) = params.insert_group { let _ = editor.insert_group(insert_params).await?; } @@ -61,24 +61,37 @@ pub(crate) async fn update_grid_setting_handler( Ok(()) } +#[tracing::instrument(level = "trace", skip(data, manager), err)] +pub(crate) async fn get_all_filters_handler( + data: Data, + manager: AppData>, +) -> DataResult { + let grid_id: GridIdPB = data.into_inner(); + let editor = manager.open_grid(grid_id).await?; + let filters = RepeatedFilterPB { + items: editor.get_all_filters().await?, + }; + data_result(filters) +} + #[tracing::instrument(level = "debug", skip(data, manager), err)] pub(crate) async fn get_grid_blocks_handler( data: Data, manager: AppData>, ) -> DataResult { let params: QueryGridBlocksParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; - let repeated_grid_block = editor.get_blocks(Some(params.block_ids)).await?; - data_result(repeated_grid_block) + let editor = manager.get_grid_editor(¶ms.grid_id).await?; + let blocks = editor.get_blocks(Some(params.block_ids)).await?; + data_result(make_block_pbs(blocks)) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_fields_handler( - data: Data, + data: Data, manager: AppData>, ) -> DataResult { - let params: QueryFieldParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let params: GetFieldParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let field_orders = params .field_ids .items @@ -92,22 +105,22 @@ pub(crate) async fn get_fields_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let changeset: FieldChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(&changeset.grid_id)?; + let editor = manager.get_grid_editor(&changeset.grid_id).await?; let _ = editor.update_field(changeset).await?; Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn update_field_type_option_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: UpdateFieldTypeOptionParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let params: TypeOptionChangesetParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor .update_field_type_option(¶ms.grid_id, ¶ms.field_id, params.type_option_data) .await?; @@ -120,23 +133,23 @@ pub(crate) async fn delete_field_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: FieldIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor.delete_field(¶ms.field_id).await?; Ok(()) } #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn switch_to_field_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: EditFieldParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; editor .switch_to_field_type(¶ms.field_id, ¶ms.field_type) .await?; - // Get the field_rev with field_id, if it doesn't exist, we create the default FieldMeta from the FieldType. + // Get the field_rev with field_id, if it doesn't exist, we create the default FieldRevision from the FieldType. let field_rev = editor .get_field_rev(¶ms.field_id) .await @@ -157,7 +170,7 @@ pub(crate) async fn duplicate_field_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: FieldIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor.duplicate_field(¶ms.field_id).await?; Ok(()) } @@ -165,17 +178,17 @@ pub(crate) async fn duplicate_field_handler( /// Return the FieldTypeOptionData if the Field exists otherwise return record not found error. #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_field_type_option_data_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let params: FieldTypeOptionIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; +) -> DataResult { + let params: TypeOptionPathParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; match editor.get_field_rev(¶ms.field_id).await { None => Err(FlowyError::record_not_found()), Some(field_rev) => { let field_type = field_rev.ty.into(); let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - let data = FieldTypeOptionDataPB { + let data = TypeOptionPB { grid_id: params.grid_id, field: field_rev.into(), type_option_data, @@ -190,16 +203,16 @@ pub(crate) async fn get_field_type_option_data_handler( pub(crate) async fn create_field_type_option_data_handler( data: Data, manager: AppData>, -) -> DataResult { +) -> DataResult { let params: CreateFieldParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let field_rev = editor .create_new_field_rev(¶ms.field_type, params.type_option_data) .await?; let field_type: FieldType = field_rev.ty.into(); let type_option_data = get_type_option_data(&field_rev, &field_type).await?; - data_result(FieldTypeOptionDataPB { + data_result(TypeOptionPB { grid_id: params.grid_id, field: field_rev.into(), type_option_data, @@ -212,7 +225,7 @@ pub(crate) async fn move_field_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: MoveFieldParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor.move_field(params).await?; Ok(()) } @@ -237,7 +250,7 @@ pub(crate) async fn get_row_handler( manager: AppData>, ) -> DataResult { let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let row = editor.get_row_rev(¶ms.row_id).await?.map(make_row_from_row_rev); data_result(OptionalRowPB { row }) @@ -249,7 +262,7 @@ pub(crate) async fn delete_row_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor.delete_row(¶ms.row_id).await?; Ok(()) } @@ -260,7 +273,7 @@ pub(crate) async fn duplicate_row_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: RowIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; let _ = editor.duplicate_row(¶ms.row_id).await?; Ok(()) } @@ -271,7 +284,7 @@ pub(crate) async fn move_row_handler( manager: AppData>, ) -> Result<(), FlowyError> { let params: MoveRowParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.view_id)?; + let editor = manager.get_grid_editor(¶ms.view_id).await?; let _ = editor.move_row(params).await?; Ok(()) } @@ -282,20 +295,20 @@ pub(crate) async fn create_table_row_handler( manager: AppData>, ) -> DataResult { let params: CreateRowParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(params.grid_id.as_ref())?; + let editor = manager.get_grid_editor(params.grid_id.as_ref()).await?; let row = editor.create_row(params).await?; data_result(row) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn get_cell_handler( - data: Data, + data: Data, manager: AppData>, -) -> DataResult { - let params: GridCellIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; +) -> DataResult { + let params: CellPathParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; match editor.get_cell(¶ms).await { - None => data_result(GridCellPB::empty(¶ms.field_id)), + None => data_result(CellPB::empty(¶ms.field_id)), Some(cell) => data_result(cell), } } @@ -306,8 +319,8 @@ pub(crate) async fn update_cell_handler( manager: AppData>, ) -> Result<(), FlowyError> { let changeset: CellChangesetPB = data.into_inner(); - let editor = manager.get_grid_editor(&changeset.grid_id)?; - let _ = editor.update_cell(changeset).await?; + let editor = manager.get_grid_editor(&changeset.grid_id).await?; + let _ = editor.update_cell_with_changeset(changeset).await?; Ok(()) } @@ -317,7 +330,7 @@ pub(crate) async fn new_select_option_handler( manager: AppData>, ) -> DataResult { let params: CreateSelectOptionParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; match editor.get_field_rev(¶ms.field_id).await { None => Err(ErrorCode::InvalidData.into()), Some(field_rev) => { @@ -330,11 +343,11 @@ pub(crate) async fn new_select_option_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let changeset: SelectOptionChangeset = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(&changeset.cell_identifier.grid_id)?; + let editor = manager.get_grid_editor(&changeset.cell_identifier.grid_id).await?; let _ = editor .modify_field_rev(&changeset.cell_identifier.field_id, |field_rev| { @@ -372,7 +385,7 @@ pub(crate) async fn update_select_option_handler( }; let cloned_editor = editor.clone(); tokio::spawn(async move { - match cloned_editor.update_cell(changeset).await { + match cloned_editor.update_cell_with_changeset(changeset).await { Ok(_) => {} Err(e) => tracing::error!("{}", e), } @@ -387,11 +400,11 @@ pub(crate) async fn update_select_option_handler( #[tracing::instrument(level = "trace", skip(data, manager), err)] pub(crate) async fn get_select_option_handler( - data: Data, + data: Data, manager: AppData>, ) -> DataResult { - let params: GridCellIdParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.grid_id)?; + let params: CellPathParams = data.into_inner().try_into()?; + let editor = manager.get_grid_editor(¶ms.grid_id).await?; match editor.get_field_rev(¶ms.field_id).await { None => { tracing::error!("Can't find the select option field with id: {}", params.field_id); @@ -401,8 +414,8 @@ pub(crate) async fn get_select_option_handler( // let cell_rev = editor.get_cell_rev(¶ms.row_id, ¶ms.field_id).await?; let type_option = select_type_option_from_field_rev(&field_rev)?; - let any_cell_data: AnyCellData = match cell_rev { - None => AnyCellData { + let any_cell_data: TypeCellData = match cell_rev { + None => TypeCellData { data: "".to_string(), field_type: field_rev.ty.into(), }, @@ -416,23 +429,32 @@ pub(crate) async fn get_select_option_handler( #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_select_option_cell_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { let params: SelectOptionCellChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.cell_identifier.grid_id)?; - let _ = editor.update_cell(params.into()).await?; + let editor = manager.get_grid_editor(¶ms.cell_identifier.grid_id).await?; + let _ = editor.update_cell_with_changeset(params.into()).await?; Ok(()) } #[tracing::instrument(level = "trace", skip_all, err)] pub(crate) async fn update_date_cell_handler( - data: Data, + data: Data, manager: AppData>, ) -> Result<(), FlowyError> { - let params: DateChangesetParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(¶ms.cell_identifier.grid_id)?; - let _ = editor.update_cell(params.into()).await?; + let data = data.into_inner(); + let cell_path: CellPathParams = data.cell_path.try_into()?; + let content = DateCellChangeset { + date: data.date, + time: data.time, + is_utc: data.is_utc, + }; + + let editor = manager.get_grid_editor(&cell_path.grid_id).await?; + let _ = editor + .update_cell(cell_path.grid_id, cell_path.row_id, cell_path.field_id, content) + .await?; Ok(()) } @@ -442,7 +464,7 @@ pub(crate) async fn get_groups_handler( manager: AppData>, ) -> DataResult { let params: GridIdPB = data.into_inner(); - let editor = manager.get_grid_editor(¶ms.value)?; + let editor = manager.get_grid_editor(¶ms.value).await?; let group = editor.load_groups().await?; data_result(group) } @@ -453,7 +475,7 @@ pub(crate) async fn create_board_card_handler( manager: AppData>, ) -> DataResult { let params: CreateRowParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(params.grid_id.as_ref())?; + let editor = manager.get_grid_editor(params.grid_id.as_ref()).await?; let row = editor.create_row(params).await?; data_result(row) } @@ -464,7 +486,7 @@ pub(crate) async fn move_group_handler( manager: AppData>, ) -> FlowyResult<()> { let params: MoveGroupParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(params.view_id.as_ref())?; + let editor = manager.get_grid_editor(params.view_id.as_ref()).await?; let _ = editor.move_group(params).await?; Ok(()) } @@ -475,7 +497,7 @@ pub(crate) async fn move_group_row_handler( manager: AppData>, ) -> FlowyResult<()> { let params: MoveGroupRowParams = data.into_inner().try_into()?; - let editor = manager.get_grid_editor(params.view_id.as_ref())?; + let editor = manager.get_grid_editor(params.view_id.as_ref()).await?; let _ = editor.move_group_row(params).await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-grid/src/event_map.rs b/frontend/rust-lib/flowy-grid/src/event_map.rs index 09ce79496f..96b7f48fce 100644 --- a/frontend/rust-lib/flowy-grid/src/event_map.rs +++ b/frontend/rust-lib/flowy-grid/src/event_map.rs @@ -12,6 +12,7 @@ pub fn create(grid_manager: Arc) -> Module { .event(GridEvent::GetGridBlocks, get_grid_blocks_handler) .event(GridEvent::GetGridSetting, get_grid_setting_handler) .event(GridEvent::UpdateGridSetting, update_grid_setting_handler) + .event(GridEvent::GetAllFilters, get_all_filters_handler) // Field .event(GridEvent::GetFields, get_fields_handler) .event(GridEvent::UpdateField, update_field_handler) @@ -74,22 +75,25 @@ pub enum GridEvent { /// [UpdateGridSetting] event is used to update the grid's settings. /// - /// The event handler accepts [GridSettingChangesetPayloadPB] and return errors if failed to modify the grid's settings. - #[event(input = "GridSettingChangesetPayloadPB")] + /// The event handler accepts [GridSettingChangesetPB] and return errors if failed to modify the grid's settings. + #[event(input = "GridSettingChangesetPB")] UpdateGridSetting = 3, + #[event(input = "GridIdPB", output = "RepeatedFilterPB")] + GetAllFilters = 4, + /// [GetFields] event is used to get the grid's settings. /// - /// The event handler accepts a [QueryFieldPayloadPB] and returns a [RepeatedFieldPB] + /// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB] /// if there are no errors. - #[event(input = "QueryFieldPayloadPB", output = "RepeatedFieldPB")] + #[event(input = "GetFieldPayloadPB", output = "RepeatedFieldPB")] GetFields = 10, /// [UpdateField] event is used to update a field's attributes. /// - /// The event handler accepts a [FieldChangesetPayloadPB] and returns errors if failed to modify the + /// The event handler accepts a [FieldChangesetPB] and returns errors if failed to modify the /// field. - #[event(input = "FieldChangesetPayloadPB")] + #[event(input = "FieldChangesetPB")] UpdateField = 11, /// [UpdateFieldTypeOption] event is used to update the field's type-option data. Certain field @@ -100,9 +104,9 @@ pub enum GridEvent { /// Check out [this](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid#fieldtype) /// for more information. /// - /// The event handler accepts a [UpdateFieldTypeOptionPayloadPB] and returns errors if failed to modify the + /// The event handler accepts a [TypeOptionChangesetPB] and returns errors if failed to modify the /// field. - #[event(input = "UpdateFieldTypeOptionPayloadPB")] + #[event(input = "TypeOptionChangesetPB")] UpdateFieldTypeOption = 12, /// [DeleteField] event is used to delete a Field. [DeleteFieldPayloadPB] is the context that @@ -113,7 +117,7 @@ pub enum GridEvent { /// [SwitchToField] event is used to update the current Field's type. /// It will insert a new FieldTypeOptionData if the new FieldType doesn't exist before, otherwise /// reuse the existing FieldTypeOptionData. You could check the [GridRevisionPad] for more details. - #[event(input = "EditFieldPayloadPB")] + #[event(input = "EditFieldChangesetPB")] SwitchToField = 20, /// [DuplicateField] event is used to duplicate a Field. The duplicated field data is kind of @@ -130,17 +134,17 @@ pub enum GridEvent { #[event(input = "MoveFieldPayloadPB")] MoveField = 22, - /// [FieldTypeOptionIdPB] event is used to get the FieldTypeOption data for a specific field type. + /// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type. /// - /// Check out the [FieldTypeOptionDataPB] for more details. If the [FieldTypeOptionData] does exist + /// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist /// for the target type, the [TypeOptionBuilder] will create the default data for that type. /// - /// Return the [FieldTypeOptionDataPB] if there are no errors. - #[event(input = "FieldTypeOptionIdPB", output = "FieldTypeOptionDataPB")] + /// Return the [TypeOptionPB] if there are no errors. + #[event(input = "TypeOptionPathPB", output = "TypeOptionPB")] GetFieldTypeOption = 23, /// [CreateFieldTypeOption] event is used to create a new FieldTypeOptionData. - #[event(input = "CreateFieldPayloadPB", output = "FieldTypeOptionDataPB")] + #[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")] CreateFieldTypeOption = 24, /// [NewSelectOption] event is used to create a new select option. Returns a [SelectOptionPB] if @@ -149,18 +153,18 @@ pub enum GridEvent { NewSelectOption = 30, /// [GetSelectOptionCellData] event is used to get the select option data for cell editing. - /// [GridCellIdPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB] + /// [CellPathPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB] /// contains the available options and the currently selected options. - #[event(input = "GridCellIdPB", output = "SelectOptionCellDataPB")] + #[event(input = "CellPathPB", output = "SelectOptionCellDataPB")] GetSelectOptionCellData = 31, /// [UpdateSelectOption] event is used to update a FieldTypeOptionData whose field_type is /// FieldType::SingleSelect or FieldType::MultiSelect. /// - /// This event may trigger the GridNotification::DidUpdateCell event. - /// For example, GridNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPayloadPB] + /// This event may trigger the GridDartNotification::DidUpdateCell event. + /// For example, GridDartNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPB] /// carries a change that updates the name of the option. - #[event(input = "SelectOptionChangesetPayloadPB")] + #[event(input = "SelectOptionChangesetPB")] UpdateSelectOption = 32, #[event(input = "CreateTableRowPayloadPB", output = "RowPB")] @@ -180,7 +184,7 @@ pub enum GridEvent { #[event(input = "MoveRowPayloadPB")] MoveRow = 54, - #[event(input = "GridCellIdPB", output = "GridCellPB")] + #[event(input = "CellPathPB", output = "CellPB")] GetCell = 70, /// [UpdateCell] event is used to update the cell content. The passed in data, [CellChangesetPB], @@ -196,16 +200,16 @@ pub enum GridEvent { #[event(input = "CellChangesetPB")] UpdateCell = 71, - /// [UpdateSelectOptionCell] event is used to update a select option cell's data. [SelectOptionCellChangesetPayloadPB] + /// [UpdateSelectOptionCell] event is used to update a select option cell's data. [SelectOptionCellChangesetPB] /// contains options that will be deleted or inserted. It can be cast to [CellChangesetPB] that /// will be used by the `update_cell` function. - #[event(input = "SelectOptionCellChangesetPayloadPB")] + #[event(input = "SelectOptionCellChangesetPB")] UpdateSelectOptionCell = 72, - /// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPayloadPB] + /// [UpdateDateCell] event is used to update a date cell's data. [DateChangesetPB] /// contains the date and the time string. It can be cast to [CellChangesetPB] that /// will be used by the `update_cell` function. - #[event(input = "DateChangesetPayloadPB")] + #[event(input = "DateChangesetPB")] UpdateDateCell = 80, #[event(input = "GridIdPB", output = "RepeatedGridGroupPB")] diff --git a/frontend/rust-lib/flowy-grid/src/manager.rs b/frontend/rust-lib/flowy-grid/src/manager.rs index fcc6c7c9d2..da8f60b5e1 100644 --- a/frontend/rust-lib/flowy-grid/src/manager.rs +++ b/frontend/rust-lib/flowy-grid/src/manager.rs @@ -1,21 +1,27 @@ use crate::entities::GridLayout; -use crate::services::block_editor::GridBlockRevisionCompactor; -use crate::services::grid_editor::{GridRevisionCompactor, GridRevisionEditor}; -use crate::services::grid_view_manager::make_grid_view_rev_manager; + +use crate::services::grid_editor::{GridRevisionCompress, GridRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::persistence::kv::GridKVPersistence; use crate::services::persistence::migration::GridMigration; +use crate::services::persistence::rev_sqlite::SQLiteGridRevisionPersistence; use crate::services::persistence::GridDatabase; -use crate::services::tasks::GridTaskScheduler; +use crate::services::view_editor::make_grid_view_rev_manager; use bytes::Bytes; -use dashmap::DashMap; + use flowy_database::ConnectionPool; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{BuildGridContext, GridRevision, GridViewRevision}; -use flowy_revision::disk::{SQLiteGridBlockRevisionPersistence, SQLiteGridRevisionPersistence}; -use flowy_revision::{RevisionManager, RevisionPersistence, RevisionWebSocket, SQLiteRevisionSnapshotPersistence}; +use flowy_http_model::revision::Revision; +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, RevisionWebSocket, + SQLiteRevisionSnapshotPersistence, +}; use flowy_sync::client_grid::{make_grid_block_operations, make_grid_operations, make_grid_view_operations}; -use flowy_sync::entities::revision::{RepeatedRevision, Revision}; +use grid_rev_model::{BuildGridContext, GridRevision, GridViewRevision}; +use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; + +use crate::services::block_manager::make_grid_block_rev_manager; +use flowy_task::TaskDispatcher; use std::sync::Arc; use tokio::sync::RwLock; @@ -25,15 +31,13 @@ pub trait GridUser: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; } -pub type GridTaskSchedulerRwLock = Arc>; - pub struct GridManager { - grid_editors: Arc>>, + grid_editors: RwLock>>, grid_user: Arc, block_index_cache: Arc, #[allow(dead_code)] kv_persistence: Arc, - task_scheduler: GridTaskSchedulerRwLock, + task_scheduler: Arc>, migration: GridMigration, } @@ -41,12 +45,12 @@ impl GridManager { pub fn new( grid_user: Arc, _rev_web_socket: Arc, + task_scheduler: Arc>, database: Arc, ) -> Self { - let grid_editors = Arc::new(DashMap::new()); + let grid_editors = RwLock::new(RefCountHashMap::new()); let kv_persistence = Arc::new(GridKVPersistence::new(database.clone())); let block_index_cache = Arc::new(BlockIndexCache::new(database.clone())); - let task_scheduler = GridTaskScheduler::new(); let migration = GridMigration::new(grid_user.clone(), database); Self { grid_editors, @@ -67,7 +71,7 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_grid>(&self, grid_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { + pub async fn create_grid>(&self, grid_id: T, revisions: Vec) -> FlowyResult<()> { let grid_id = grid_id.as_ref(); let db_pool = self.grid_user.db_pool()?; let rev_manager = self.make_grid_rev_manager(grid_id, db_pool)?; @@ -77,7 +81,7 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, err)] - async fn create_grid_view>(&self, view_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { + async fn create_grid_view>(&self, view_id: T, revisions: Vec) -> FlowyResult<()> { let view_id = view_id.as_ref(); let rev_manager = make_grid_view_rev_manager(&self.grid_user, view_id).await?; let _ = rev_manager.reset_object(revisions).await?; @@ -85,10 +89,9 @@ impl GridManager { } #[tracing::instrument(level = "debug", skip_all, err)] - pub async fn create_grid_block>(&self, block_id: T, revisions: RepeatedRevision) -> FlowyResult<()> { + pub async fn create_grid_block>(&self, block_id: T, revisions: Vec) -> FlowyResult<()> { let block_id = block_id.as_ref(); - let db_pool = self.grid_user.db_pool()?; - let rev_manager = self.make_grid_block_rev_manager(block_id, db_pool)?; + let rev_manager = make_grid_block_rev_manager(&self.grid_user, block_id)?; let _ = rev_manager.reset_object(revisions).await?; Ok(()) } @@ -104,34 +107,30 @@ impl GridManager { pub async fn close_grid>(&self, grid_id: T) -> FlowyResult<()> { let grid_id = grid_id.as_ref(); tracing::Span::current().record("grid_id", &grid_id); - self.grid_editors.remove(grid_id); - self.task_scheduler.write().await.unregister_handler(grid_id); + + self.grid_editors.write().await.remove(grid_id); + // self.task_scheduler.write().await.unregister_handler(grid_id); Ok(()) } // #[tracing::instrument(level = "debug", skip(self), err)] - pub fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { - match self.grid_editors.get(grid_id) { + pub async fn get_grid_editor(&self, grid_id: &str) -> FlowyResult> { + match self.grid_editors.read().await.get(grid_id) { None => Err(FlowyError::internal().context("Should call open_grid function first")), - Some(editor) => Ok(editor.clone()), + Some(editor) => Ok(editor), } } async fn get_or_create_grid_editor(&self, grid_id: &str) -> FlowyResult> { - match self.grid_editors.get(grid_id) { - None => { - let db_pool = self.grid_user.db_pool()?; - let editor = self.make_grid_rev_editor(grid_id, db_pool).await?; - - if self.grid_editors.contains_key(grid_id) { - tracing::warn!("Grid:{} already exists in cache", grid_id); - } - self.grid_editors.insert(grid_id.to_string(), editor.clone()); - self.task_scheduler.write().await.register_handler(editor.clone()); - Ok(editor) - } - Some(editor) => Ok(editor.clone()), + if let Some(editor) = self.grid_editors.read().await.get(grid_id) { + return Ok(editor); } + + let mut grid_editors = self.grid_editors.write().await; + let db_pool = self.grid_user.db_pool()?; + let editor = self.make_grid_rev_editor(grid_id, db_pool).await?; + grid_editors.insert(grid_id.to_string(), editor.clone()); + Ok(editor) } #[tracing::instrument(level = "trace", skip(self, pool), err)] @@ -153,30 +152,24 @@ impl GridManager { Ok(grid_editor) } - pub fn make_grid_rev_manager(&self, grid_id: &str, pool: Arc) -> FlowyResult { + pub fn make_grid_rev_manager( + &self, + grid_id: &str, + pool: Arc, + ) -> FlowyResult>> { let user_id = self.grid_user.user_id()?; let disk_cache = SQLiteGridRevisionPersistence::new(&user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(&user_id, grid_id, disk_cache); + let configuration = RevisionPersistenceConfiguration::new(2, false); + let rev_persistence = RevisionPersistence::new(&user_id, grid_id, disk_cache, configuration); let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(grid_id, pool); - let rev_compactor = GridRevisionCompactor(); + let rev_compactor = GridRevisionCompress(); let rev_manager = RevisionManager::new(&user_id, grid_id, rev_persistence, rev_compactor, snapshot_persistence); Ok(rev_manager) } - - fn make_grid_block_rev_manager(&self, block_id: &str, pool: Arc) -> FlowyResult { - let user_id = self.grid_user.user_id()?; - let disk_cache = SQLiteGridBlockRevisionPersistence::new(&user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache); - let rev_compactor = GridBlockRevisionCompactor(); - let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(block_id, pool); - let rev_manager = - RevisionManager::new(&user_id, block_id, rev_persistence, rev_compactor, snapshot_persistence); - Ok(rev_manager) - } } pub async fn make_grid_view_data( - user_id: &str, + _user_id: &str, view_id: &str, layout: GridLayout, grid_manager: Arc, @@ -199,9 +192,8 @@ pub async fn make_grid_view_data( // Create grid's block let grid_block_delta = make_grid_block_operations(block_meta_data); let block_delta_data = grid_block_delta.json_bytes(); - let repeated_revision: RepeatedRevision = - Revision::initial_revision(user_id, block_id, block_delta_data).into(); - let _ = grid_manager.create_grid_block(&block_id, repeated_revision).await?; + let revision = Revision::initial_revision(block_id, block_delta_data); + let _ = grid_manager.create_grid_block(&block_id, vec![revision]).await?; } // Will replace the grid_id with the value returned by the gen_grid_id() @@ -211,9 +203,8 @@ pub async fn make_grid_view_data( // Create grid let grid_rev_delta = make_grid_operations(&grid_rev); let grid_rev_delta_bytes = grid_rev_delta.json_bytes(); - let repeated_revision: RepeatedRevision = - Revision::initial_revision(user_id, &grid_id, grid_rev_delta_bytes.clone()).into(); - let _ = grid_manager.create_grid(&grid_id, repeated_revision).await?; + let revision = Revision::initial_revision(&grid_id, grid_rev_delta_bytes.clone()); + let _ = grid_manager.create_grid(&grid_id, vec![revision]).await?; // Create grid view let grid_view = if grid_view_revision_data.is_empty() { @@ -223,9 +214,14 @@ pub async fn make_grid_view_data( }; let grid_view_delta = make_grid_view_operations(&grid_view); let grid_view_delta_bytes = grid_view_delta.json_bytes(); - let repeated_revision: RepeatedRevision = - Revision::initial_revision(user_id, view_id, grid_view_delta_bytes).into(); - let _ = grid_manager.create_grid_view(view_id, repeated_revision).await?; + let revision = Revision::initial_revision(view_id, grid_view_delta_bytes); + let _ = grid_manager.create_grid_view(view_id, vec![revision]).await?; Ok(grid_rev_delta_bytes) } + +impl RefCountValue for GridRevisionEditor { + fn did_remove(&self) { + self.close(); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/block_editor.rs b/frontend/rust-lib/flowy-grid/src/services/block_editor.rs index 1797a08150..b8e1fc1874 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_editor.rs @@ -1,25 +1,27 @@ use crate::entities::RowPB; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, GridBlockRevision, RowChangeset, RowRevision}; +use flowy_http_model::revision::Revision; use flowy_revision::{ - RevisionCloudService, RevisionCompress, RevisionManager, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, }; use flowy_sync::client_grid::{GridBlockRevisionChangeset, GridBlockRevisionPad}; -use flowy_sync::entities::revision::Revision; use flowy_sync::util::make_operations_from_revisions; +use grid_rev_model::{CellRevision, GridBlockRevision, RowChangeset, RowRevision}; use lib_infra::future::FutureResult; +use flowy_database::ConnectionPool; use lib_ot::core::EmptyAttributes; use std::borrow::Cow; use std::sync::Arc; use tokio::sync::RwLock; pub struct GridBlockRevisionEditor { + #[allow(dead_code)] user_id: String, pub block_id: String, pad: Arc>, - rev_manager: Arc, + rev_manager: Arc>>, } impl GridBlockRevisionEditor { @@ -27,12 +29,12 @@ impl GridBlockRevisionEditor { user_id: &str, token: &str, block_id: &str, - mut rev_manager: RevisionManager, + mut rev_manager: RevisionManager>, ) -> FlowyResult { let cloud = Arc::new(GridBlockRevisionCloudService { token: token.to_owned(), }); - let block_revision_pad = rev_manager.load::(Some(cloud)).await?; + let block_revision_pad = rev_manager.initialize::(Some(cloud)).await?; let pad = Arc::new(RwLock::new(block_revision_pad)); let rev_manager = Arc::new(rev_manager); let user_id = user_id.to_owned(); @@ -132,10 +134,10 @@ impl GridBlockRevisionEditor { pub async fn get_row_pb(&self, row_id: &str) -> FlowyResult> { let row_ids = Some(vec![Cow::Borrowed(row_id)]); - Ok(self.get_row_infos(row_ids).await?.pop()) + Ok(self.get_row_pbs(row_ids).await?.pop()) } - pub async fn get_row_infos(&self, row_ids: Option>>) -> FlowyResult> + pub async fn get_row_pbs(&self, row_ids: Option>>) -> FlowyResult> where T: AsRef + ToOwned + ?Sized, { @@ -166,17 +168,9 @@ impl GridBlockRevisionEditor { async fn apply_change(&self, change: GridBlockRevisionChangeset) -> FlowyResult<()> { let GridBlockRevisionChangeset { operations: delta, md5 } = change; - let user_id = self.user_id.clone(); let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); let delta_data = delta.json_bytes(); - let revision = Revision::new( - &self.rev_manager.object_id, - base_rev_id, - rev_id, - delta_data, - &user_id, - md5, - ); + let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, delta_data, md5); let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(()) } @@ -204,15 +198,15 @@ impl RevisionObjectDeserializer for GridBlockRevisionSerde { } impl RevisionObjectSerializer for GridBlockRevisionSerde { - fn serialize_revisions(revisions: Vec) -> FlowyResult { + fn combine_revisions(revisions: Vec) -> FlowyResult { let operations = make_operations_from_revisions::(revisions)?; Ok(operations.json_bytes()) } } -pub struct GridBlockRevisionCompactor(); -impl RevisionCompress for GridBlockRevisionCompactor { - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult { - GridBlockRevisionSerde::serialize_revisions(revisions) +pub struct GridBlockRevisionCompress(); +impl RevisionMergeable for GridBlockRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + GridBlockRevisionSerde::combine_revisions(revisions) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs index fe825c2ebc..18addf2620 100644 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager.rs +++ b/frontend/rust-lib/flowy-grid/src/services/block_manager.rs @@ -1,16 +1,17 @@ -use crate::dart_notification::{send_dart_notification, GridNotification}; +use crate::dart_notification::{send_dart_notification, GridDartNotification}; use crate::entities::{CellChangesetPB, GridBlockChangesetPB, InsertedRowPB, RowPB}; use crate::manager::GridUser; -use crate::services::block_editor::{GridBlockRevisionCompactor, GridBlockRevisionEditor}; +use crate::services::block_editor::{GridBlockRevisionCompress, GridBlockRevisionEditor}; use crate::services::persistence::block_index::BlockIndexCache; -use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlockSnapshot}; +use crate::services::persistence::rev_sqlite::SQLiteGridBlockRevisionPersistence; +use crate::services::row::{block_from_row_orders, make_row_from_row_rev, GridBlock}; use dashmap::DashMap; +use flowy_database::ConnectionPool; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{ - GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision, +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, SQLiteRevisionSnapshotPersistence, }; -use flowy_revision::disk::SQLiteGridBlockRevisionPersistence; -use flowy_revision::{RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence}; +use grid_rev_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision}; use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; @@ -44,7 +45,7 @@ impl GridBlockManager { match self.block_editors.get(block_id) { None => { tracing::error!("This is a fatal error, block with id:{} is not exist", block_id); - let editor = Arc::new(make_block_editor(&self.user, block_id).await?); + let editor = Arc::new(make_grid_block_editor(&self.user, block_id).await?); self.block_editors.insert(block_id.to_owned(), editor.clone()); Ok(editor) } @@ -208,38 +209,35 @@ impl GridBlockManager { } } - pub async fn get_row_orders(&self, block_id: &str) -> FlowyResult> { + pub async fn get_row_revs(&self, block_id: &str) -> FlowyResult>> { let editor = self.get_block_editor(block_id).await?; - editor.get_row_infos::<&str>(None).await + editor.get_row_revs::<&str>(None).await } - pub(crate) async fn get_block_snapshots( - &self, - block_ids: Option>, - ) -> FlowyResult> { - let mut snapshots = vec![]; + pub(crate) async fn get_blocks(&self, block_ids: Option>) -> FlowyResult> { + let mut blocks = vec![]; match block_ids { None => { for iter in self.block_editors.iter() { let editor = iter.value(); let block_id = editor.block_id.clone(); let row_revs = editor.get_row_revs::<&str>(None).await?; - snapshots.push(GridBlockSnapshot { block_id, row_revs }); + blocks.push(GridBlock { block_id, row_revs }); } } Some(block_ids) => { for block_id in block_ids { let editor = self.get_block_editor(&block_id).await?; let row_revs = editor.get_row_revs::<&str>(None).await?; - snapshots.push(GridBlockSnapshot { block_id, row_revs }); + blocks.push(GridBlock { block_id, row_revs }); } } } - Ok(snapshots) + Ok(blocks) } async fn notify_did_update_block(&self, block_id: &str, changeset: GridBlockChangesetPB) -> FlowyResult<()> { - send_dart_notification(block_id, GridNotification::DidUpdateGridBlock) + send_dart_notification(block_id, GridDartNotification::DidUpdateGridBlock) .payload(changeset) .send(); Ok(()) @@ -247,7 +245,7 @@ impl GridBlockManager { async fn notify_did_update_cell(&self, changeset: CellChangesetPB) -> FlowyResult<()> { let id = format!("{}:{}", changeset.row_id, changeset.field_id); - send_dart_notification(&id, GridNotification::DidUpdateCell).send(); + send_dart_notification(&id, GridDartNotification::DidUpdateCell).send(); Ok(()) } } @@ -259,23 +257,32 @@ async fn make_block_editors( ) -> FlowyResult>> { let editor_map = DashMap::new(); for block_meta_rev in block_meta_revs { - let editor = make_block_editor(user, &block_meta_rev.block_id).await?; + let editor = make_grid_block_editor(user, &block_meta_rev.block_id).await?; editor_map.insert(block_meta_rev.block_id.clone(), Arc::new(editor)); } Ok(editor_map) } -async fn make_block_editor(user: &Arc, block_id: &str) -> FlowyResult { +async fn make_grid_block_editor(user: &Arc, block_id: &str) -> FlowyResult { tracing::trace!("Open block:{} editor", block_id); let token = user.token()?; let user_id = user.user_id()?; - let pool = user.db_pool()?; - - let disk_cache = SQLiteGridBlockRevisionPersistence::new(&user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache); - let rev_compactor = GridBlockRevisionCompactor(); - let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(block_id, pool); - let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence, rev_compactor, snapshot_persistence); + let rev_manager = make_grid_block_rev_manager(user, block_id)?; GridBlockRevisionEditor::new(&user_id, &token, block_id, rev_manager).await } + +pub fn make_grid_block_rev_manager( + user: &Arc, + block_id: &str, +) -> FlowyResult>> { + let user_id = user.user_id()?; + let pool = user.db_pool()?; + let disk_cache = SQLiteGridBlockRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(4, false); + let rev_persistence = RevisionPersistence::new(&user_id, block_id, disk_cache, configuration); + let rev_compactor = GridBlockRevisionCompress(); + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(block_id, pool); + let rev_manager = RevisionManager::new(&user_id, block_id, rev_persistence, rev_compactor, snapshot_persistence); + Ok(rev_manager) +} diff --git a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs deleted file mode 100644 index 6342ba0099..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/block_manager_trait_impl.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::services::block_manager::GridBlockManager; -use crate::services::grid_view_manager::GridViewRowDelegate; - -use flowy_grid_data_model::revision::RowRevision; -use lib_infra::future::{wrap_future, AFFuture}; -use std::sync::Arc; - -impl GridViewRowDelegate for Arc { - fn gv_index_of_row(&self, row_id: &str) -> AFFuture> { - let block_manager = self.clone(); - let row_id = row_id.to_owned(); - wrap_future(async move { block_manager.index_of_row(&row_id).await }) - } - - fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>> { - let block_manager = self.clone(); - let row_id = row_id.to_owned(); - wrap_future(async move { - match block_manager.get_row_rev(&row_id).await { - Ok(row_rev) => row_rev, - Err(_) => None, - } - }) - } - - fn gv_row_revs(&self) -> AFFuture>> { - let block_manager = self.clone(); - - wrap_future(async move { - let blocks = block_manager.get_block_snapshots(None).await.unwrap(); - blocks - .into_iter() - .flat_map(|block| block.row_revs) - .collect::>>() - }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs index 1f64ca9ebb..098c67a150 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/any_cell_data.rs @@ -2,24 +2,33 @@ use crate::entities::FieldType; use crate::services::cell::{CellData, FromCellString}; use bytes::Bytes; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::CellRevision; +use grid_rev_model::CellRevision; use serde::{Deserialize, Serialize}; use std::str::FromStr; -/// AnyCellData is a generic CellData, you can parse the cell_data according to the field_type. +/// TypeCellData is a generic CellData, you can parse the cell_data according to the field_type. /// When the type of field is changed, it's different from the field_type of AnyCellData. /// So it will return an empty data. You could check the CellDataOperation trait for more information. #[derive(Debug, Serialize, Deserialize)] -pub struct AnyCellData { +pub struct TypeCellData { pub data: String, pub field_type: FieldType, } -impl std::str::FromStr for AnyCellData { +impl TypeCellData { + pub fn from_field_type(field_type: &FieldType) -> TypeCellData { + Self { + data: "".to_string(), + field_type: field_type.clone(), + } + } +} + +impl std::str::FromStr for TypeCellData { type Err = FlowyError; fn from_str(s: &str) -> Result { - let type_option_cell_data: AnyCellData = serde_json::from_str(s).map_err(|err| { + let type_option_cell_data: TypeCellData = serde_json::from_str(s).map_err(|err| { let msg = format!("Deserialize {} to any cell data failed. Serde error: {}", s, err); FlowyError::internal().context(msg) })?; @@ -27,15 +36,15 @@ impl std::str::FromStr for AnyCellData { } } -impl std::convert::TryInto for String { +impl std::convert::TryInto for String { type Error = FlowyError; - fn try_into(self) -> Result { - AnyCellData::from_str(&self) + fn try_into(self) -> Result { + TypeCellData::from_str(&self) } } -impl std::convert::TryFrom<&CellRevision> for AnyCellData { +impl std::convert::TryFrom<&CellRevision> for TypeCellData { type Error = FlowyError; fn try_from(value: &CellRevision) -> Result { @@ -43,7 +52,7 @@ impl std::convert::TryFrom<&CellRevision> for AnyCellData { } } -impl std::convert::TryFrom for AnyCellData { +impl std::convert::TryFrom for TypeCellData { type Error = FlowyError; fn try_from(value: CellRevision) -> Result { @@ -51,24 +60,24 @@ impl std::convert::TryFrom for AnyCellData { } } -impl std::convert::From for CellData +impl std::convert::From for CellData where T: FromCellString, { - fn from(any_call_data: AnyCellData) -> Self { + fn from(any_call_data: TypeCellData) -> Self { CellData::from(any_call_data.data) } } -impl AnyCellData { +impl TypeCellData { pub fn new(content: String, field_type: FieldType) -> Self { - AnyCellData { + TypeCellData { data: content, field_type, } } - pub fn json(&self) -> String { + pub fn to_json(&self) -> String { serde_json::to_string(self).unwrap_or_else(|_| "".to_owned()) } diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs index 95c9c2f5bb..5a564b2b67 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs @@ -1,19 +1,19 @@ use crate::entities::FieldType; -use crate::services::cell::{AnyCellData, CellBytes}; +use crate::services::cell::{CellBytes, TypeCellData}; use crate::services::field::*; use std::fmt::Debug; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, FieldTypeRevision}; +use grid_rev_model::{CellRevision, FieldRevision, FieldTypeRevision}; /// This trait is used when doing filter/search on the grid. pub trait CellFilterOperation { /// Return true if any_cell_data match the filter condition. - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &T) -> FlowyResult; + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &T) -> FlowyResult; } pub trait CellGroupOperation { - fn apply_group(&self, any_cell_data: AnyCellData, group_content: &str) -> FlowyResult; + fn apply_group(&self, any_cell_data: TypeCellData, group_content: &str) -> FlowyResult; } /// Return object that describes the cell. @@ -97,7 +97,7 @@ pub trait CellDataOperation { /// For example: /// SelectOptionCellChangeset,DateCellChangeset. etc. /// - fn apply_changeset(&self, changeset: CellDataChangeset, cell_rev: Option) -> FlowyResult; + fn apply_changeset(&self, changeset: AnyCellChangeset, cell_rev: Option) -> FlowyResult; } /// changeset: It will be deserialized into specific data base on the FieldType. @@ -126,17 +126,17 @@ pub fn apply_cell_data_changeset>( FieldType::URL => URLTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev), }?; - Ok(AnyCellData::new(s, field_type).json()) + Ok(TypeCellData::new(s, field_type).to_json()) } -pub fn decode_any_cell_data + Debug>( +pub fn decode_any_cell_data + Debug>( data: T, field_rev: &FieldRevision, ) -> (FieldType, CellBytes) { let to_field_type = field_rev.ty.into(); match data.try_into() { Ok(any_cell_data) => { - let AnyCellData { data, field_type } = any_cell_data; + let TypeCellData { data, field_type } = any_cell_data; match try_decode_cell_data(data.into(), &field_type, &to_field_type, field_rev) { Ok(cell_bytes) => (field_type, cell_bytes), Err(e) => { @@ -276,9 +276,10 @@ pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRe } pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevision { - let cell_data = serde_json::to_string(&DateCellChangesetPB { + let cell_data = serde_json::to_string(&DateCellChangeset { date: Some(timestamp.to_string()), time: None, + is_utc: true, }) .unwrap(); let data = apply_cell_data_changeset(cell_data, None, field_rev).unwrap(); @@ -356,9 +357,9 @@ pub trait FromCellChangeset { Self: Sized; } -pub struct CellDataChangeset(pub Option); +pub struct AnyCellChangeset(pub Option); -impl CellDataChangeset { +impl AnyCellChangeset { pub fn try_into_inner(self) -> FlowyResult { match self.0 { None => Err(ErrorCode::InvalidData.into()), @@ -367,22 +368,22 @@ impl CellDataChangeset { } } -impl std::convert::From for CellDataChangeset +impl std::convert::From for AnyCellChangeset where T: FromCellChangeset, { fn from(changeset: C) -> Self { match T::from_changeset(changeset.to_string()) { - Ok(data) => CellDataChangeset(Some(data)), + Ok(data) => AnyCellChangeset(Some(data)), Err(e) => { tracing::error!("Deserialize CellDataChangeset failed: {}", e); - CellDataChangeset(None) + AnyCellChangeset(None) } } } } -impl std::convert::From for CellDataChangeset { +impl std::convert::From for AnyCellChangeset { fn from(s: String) -> Self { - CellDataChangeset(Some(s)) + AnyCellChangeset(Some(s)) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs index 772371d561..5f232d1232 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs @@ -2,7 +2,7 @@ use crate::entities::{FieldPB, FieldType}; use crate::services::field::{default_type_option_builder_from_type, TypeOptionBuilder}; -use flowy_grid_data_model::revision::FieldRevision; +use grid_rev_model::FieldRevision; use indexmap::IndexMap; pub struct FieldBuilder { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs index e74712d821..3c358af6f6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/field_operation.rs @@ -1,7 +1,7 @@ use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; use crate::services::grid_editor::GridRevisionEditor; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{TypeOptionDataDeserializer, TypeOptionDataSerializer}; +use grid_rev_model::{TypeOptionDataDeserializer, TypeOptionDataSerializer}; use std::sync::Arc; pub async fn edit_field_type_option( diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs index c2b2c549fa..dfedde49aa 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_option_builder.rs @@ -1,7 +1,7 @@ use crate::entities::FieldType; use crate::services::field::type_options::*; use bytes::Bytes; -use flowy_grid_data_model::revision::TypeOptionDataSerializer; +use grid_rev_model::TypeOptionDataSerializer; pub trait TypeOptionBuilder { /// Returns the type of the type-option data diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs similarity index 61% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs index 3239ff449d..c6b6c4c138 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_filter.rs @@ -1,20 +1,20 @@ -use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB}; -use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB}; +use crate::services::cell::{CellData, CellFilterOperation, TypeCellData}; use crate::services::field::{CheckboxCellData, CheckboxTypeOptionPB}; use flowy_error::FlowyResult; -impl CheckboxFilterConfigurationPB { +impl CheckboxFilterPB { pub fn is_visible(&self, cell_data: &CheckboxCellData) -> bool { let is_check = cell_data.is_check(); match self.condition { - CheckboxCondition::IsChecked => is_check, - CheckboxCondition::IsUnChecked => !is_check, + CheckboxFilterCondition::IsChecked => is_check, + CheckboxFilterCondition::IsUnChecked => !is_check, } } } -impl CellFilterOperation for CheckboxTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &CheckboxFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for CheckboxTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &CheckboxFilterPB) -> FlowyResult { if !any_cell_data.is_checkbox() { return Ok(true); } @@ -26,14 +26,14 @@ impl CellFilterOperation for CheckboxTypeOptionPB #[cfg(test)] mod tests { - use crate::entities::{CheckboxCondition, CheckboxFilterConfigurationPB}; + use crate::entities::{CheckboxFilterCondition, CheckboxFilterPB}; use crate::services::field::CheckboxCellData; use std::str::FromStr; #[test] fn checkbox_filter_is_check_test() { - let checkbox_filter = CheckboxFilterConfigurationPB { - condition: CheckboxCondition::IsChecked, + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterCondition::IsChecked, }; for (value, visible) in [("true", true), ("yes", true), ("false", false), ("no", false)] { let data = CheckboxCellData::from_str(value).unwrap(); @@ -43,8 +43,8 @@ mod tests { #[test] fn checkbox_filter_is_uncheck_test() { - let checkbox_filter = CheckboxFilterConfigurationPB { - condition: CheckboxCondition::IsUnChecked, + let checkbox_filter = CheckboxFilterPB { + condition: CheckboxFilterCondition::IsUnChecked, }; for (value, visible) in [("false", true), ("no", true), ("true", false), ("yes", false)] { let data = CheckboxCellData::from_str(value).unwrap(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs index 838e341666..9512d5d229 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_tests.rs @@ -4,7 +4,7 @@ mod tests { use crate::services::cell::CellDataOperation; use crate::services::field::type_options::checkbox_type_option::*; use crate::services::field::FieldBuilder; - use flowy_grid_data_model::revision::FieldRevision; + use grid_rev_model::FieldRevision; #[test] fn checkout_box_description_test() { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index 399dffbb3a..f677021e3a 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs @@ -1,13 +1,11 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::{BoxTypeOptionBuilder, CheckboxCellData, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -82,7 +80,7 @@ impl CellDataOperation for CheckboxTypeOptionPB { fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let changeset = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs index ebe5d1a6a8..309072caa6 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod checkbox_filter; mod checkbox_tests; mod checkbox_type_option; mod checkbox_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs new file mode 100644 index 0000000000..4785fcc5ae --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_filter.rs @@ -0,0 +1,163 @@ +use crate::entities::{DateFilterCondition, DateFilterPB}; +use crate::services::cell::{CellData, CellFilterOperation, TypeCellData}; +use crate::services::field::{DateTimestamp, DateTypeOptionPB}; +use chrono::NaiveDateTime; +use flowy_error::FlowyResult; + +impl DateFilterPB { + pub fn is_visible>>(&self, cell_timestamp: T) -> bool { + match cell_timestamp.into() { + None => DateFilterCondition::DateIsEmpty == self.condition, + Some(timestamp) => { + match self.condition { + DateFilterCondition::DateIsNotEmpty => { + return true; + } + DateFilterCondition::DateIsEmpty => { + return false; + } + _ => {} + } + + let cell_time = NaiveDateTime::from_timestamp(timestamp, 0); + let cell_date = cell_time.date(); + match self.timestamp { + None => { + if self.start.is_none() { + return true; + } + + if self.end.is_none() { + return true; + } + + let start_time = NaiveDateTime::from_timestamp(*self.start.as_ref().unwrap(), 0); + let start_date = start_time.date(); + + let end_time = NaiveDateTime::from_timestamp(*self.end.as_ref().unwrap(), 0); + let end_date = end_time.date(); + + cell_date >= start_date && cell_date <= end_date + } + Some(timestamp) => { + let expected_timestamp = NaiveDateTime::from_timestamp(timestamp, 0); + let expected_date = expected_timestamp.date(); + + // We assume that the cell_timestamp doesn't contain hours, just day. + match self.condition { + DateFilterCondition::DateIs => cell_date == expected_date, + DateFilterCondition::DateBefore => cell_date < expected_date, + DateFilterCondition::DateAfter => cell_date > expected_date, + DateFilterCondition::DateOnOrBefore => cell_date <= expected_date, + DateFilterCondition::DateOnOrAfter => cell_date >= expected_date, + _ => true, + } + } + } + } + } + } +} + +impl CellFilterOperation for DateTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &DateFilterPB) -> FlowyResult { + if !any_cell_data.is_date() { + return Ok(true); + } + let cell_data: CellData = any_cell_data.into(); + let timestamp = cell_data.try_into_inner()?; + Ok(filter.is_visible(timestamp)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{DateFilterCondition, DateFilterPB}; + + #[test] + fn date_filter_is_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateIs, + timestamp: Some(1668387885), + end: None, + start: None, + }; + + for (val, visible) in vec![(1668387885, true), (1647251762, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_before_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateBefore, + timestamp: Some(1668387885), + start: None, + end: None, + }; + + for (val, visible, msg) in vec![(1668387884, false, "1"), (1647251762, true, "2")] { + assert_eq!(filter.is_visible(val as i64), visible, "{}", msg); + } + } + + #[test] + fn date_filter_before_or_on_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateOnOrBefore, + timestamp: Some(1668387885), + start: None, + end: None, + }; + + for (val, visible) in vec![(1668387884, true), (1668387885, true)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + #[test] + fn date_filter_after_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateAfter, + timestamp: Some(1668387885), + start: None, + end: None, + }; + + for (val, visible) in vec![(1668387888, false), (1668531885, true), (0, false)] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + + #[test] + fn date_filter_within_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateWithIn, + start: Some(1668272685), // 11/13 + end: Some(1668618285), // 11/17 + timestamp: None, + }; + + for (val, visible, _msg) in vec![ + (1668272685, true, "11/13"), + (1668359085, true, "11/14"), + (1668704685, false, "11/18"), + ] { + assert_eq!(filter.is_visible(val as i64), visible); + } + } + + #[test] + fn date_filter_is_empty_test() { + let filter = DateFilterPB { + condition: DateFilterCondition::DateIsEmpty, + start: None, + end: None, + timestamp: None, + }; + + for (val, visible) in vec![(None, true), (Some(123), false)] { + assert_eq!(filter.is_visible(val), visible); + } + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs index 2e08b91d1c..db933b98d8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_tests.rs @@ -4,7 +4,9 @@ mod tests { use crate::services::cell::CellDataOperation; use crate::services::field::*; // use crate::services::field::{DateCellChangeset, DateCellData, DateFormat, DateTypeOptionPB, TimeFormat}; - use flowy_grid_data_model::revision::FieldRevision; + use chrono::format::strftime::StrftimeItems; + use chrono::{FixedOffset, NaiveDateTime}; + use grid_rev_model::FieldRevision; use strum::IntoEnumIterator; #[test] @@ -113,6 +115,30 @@ mod tests { &field_rev, ); } + + #[test] + fn utc_to_native_test() { + let native_timestamp = 1647251762; + let native = NaiveDateTime::from_timestamp(native_timestamp, 0); + + let utc = chrono::DateTime::::from_utc(native, chrono::Utc); + // utc_timestamp doesn't carry timezone + let utc_timestamp = utc.timestamp(); + assert_eq!(native_timestamp, utc_timestamp); + + let format = "%m/%d/%Y %I:%M %p".to_string(); + let native_time_str = format!("{}", native.format_with_items(StrftimeItems::new(&format))); + let utc_time_str = format!("{}", utc.format_with_items(StrftimeItems::new(&format))); + assert_eq!(native_time_str, utc_time_str); + + // Mon Mar 14 2022 17:56:02 GMT+0800 (China Standard Time) + let gmt_8_offset = FixedOffset::east(8 * 3600); + let china_local = chrono::DateTime::::from_utc(native, gmt_8_offset); + let china_local_time = format!("{}", china_local.format_with_items(StrftimeItems::new(&format))); + + assert_eq!(china_local_time, "03/14/2022 05:56 PM"); + } + fn assert_date( type_option: &DateTypeOptionPB, timestamp: T, @@ -120,9 +146,10 @@ mod tests { expected_str: &str, field_rev: &FieldRevision, ) { - let s = serde_json::to_string(&DateCellChangesetPB { + let s = serde_json::to_string(&DateCellChangeset { date: Some(timestamp.to_string()), time: include_time_str, + is_utc: false, }) .unwrap(); let encoded_data = type_option.apply_changeset(s.into(), None).unwrap(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs index 6a468b92f9..18b5a894cf 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option.rs @@ -1,17 +1,15 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::{ - BoxTypeOptionBuilder, DateCellChangesetPB, DateCellDataPB, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder, + BoxTypeOptionBuilder, DateCellChangeset, DateCellDataPB, DateFormat, DateTimestamp, TimeFormat, TypeOptionBuilder, }; use bytes::Bytes; use chrono::format::strftime::StrftimeItems; use chrono::{NaiveDateTime, Timelike}; use flowy_derive::ProtoBuf; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use serde::{Deserialize, Serialize}; // Date @@ -34,13 +32,9 @@ impl DateTypeOptionPB { Self::default() } - fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { - let timestamp = *timestamp.as_ref(); + fn today_desc_from_timestamp>(&self, timestamp: T) -> DateCellDataPB { + let timestamp = timestamp.into(); let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0); - self.date_from_native(native) - } - - fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellDataPB { if native.timestamp() == 0 { return DateCellDataPB::default(); } @@ -108,11 +102,6 @@ impl DateTypeOptionPB { Ok(utc.timestamp()) } - fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime { - let native = NaiveDateTime::from_timestamp(timestamp, 0); - self.utc_date_time_from_native(native) - } - fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime { chrono::DateTime::::from_utc(naive, chrono::Utc) } @@ -142,7 +131,7 @@ impl CellDisplayable for DateTypeOptionPB { } } -impl CellDataOperation for DateTypeOptionPB { +impl CellDataOperation for DateTypeOptionPB { fn decode_cell_data( &self, cell_data: CellData, @@ -161,7 +150,7 @@ impl CellDataOperation for DateTypeOptionPB fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let changeset = changeset.try_into_inner()?; @@ -170,7 +159,9 @@ impl CellDataOperation for DateTypeOptionPB Some(date_timestamp) => match (self.include_time, changeset.time) { (true, Some(time)) => { let time = Some(time.trim().to_uppercase()); - let utc = self.utc_date_time_from_timestamp(date_timestamp); + let native = NaiveDateTime::from_timestamp(date_timestamp, 0); + + let utc = self.utc_date_time_from_native(native); self.timestamp_from_utc_with_time(&utc, &time)? } _ => date_timestamp, diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs index 5b73176796..05b23f93f5 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs @@ -1,11 +1,8 @@ -use crate::entities::CellChangesetPB; -use crate::entities::{GridCellIdPB, GridCellIdParams}; +use crate::entities::CellPathPB; use crate::services::cell::{CellBytesParser, CellDataIsEmpty, FromCellChangeset, FromCellString}; use bytes::Bytes; - use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use flowy_error::{internal_error, ErrorCode, FlowyResult}; - +use flowy_error::{internal_error, FlowyResult}; use serde::{Deserialize, Serialize}; use strum_macros::EnumIter; @@ -22,59 +19,28 @@ pub struct DateCellDataPB { } #[derive(Clone, Debug, Default, ProtoBuf)] -pub struct DateChangesetPayloadPB { +pub struct DateChangesetPB { #[pb(index = 1)] - pub cell_identifier: GridCellIdPB, + pub cell_path: CellPathPB, #[pb(index = 2, one_of)] pub date: Option, #[pb(index = 3, one_of)] pub time: Option, -} -pub struct DateChangesetParams { - pub cell_identifier: GridCellIdParams, - pub date: Option, - pub time: Option, -} - -impl TryInto for DateChangesetPayloadPB { - type Error = ErrorCode; - - fn try_into(self) -> Result { - let cell_identifier: GridCellIdParams = self.cell_identifier.try_into()?; - Ok(DateChangesetParams { - cell_identifier, - date: self.date, - time: self.time, - }) - } -} - -impl std::convert::From for CellChangesetPB { - fn from(params: DateChangesetParams) -> Self { - let changeset = DateCellChangesetPB { - date: params.date, - time: params.time, - }; - let content = serde_json::to_string(&changeset).unwrap(); - CellChangesetPB { - grid_id: params.cell_identifier.grid_id, - row_id: params.cell_identifier.row_id, - field_id: params.cell_identifier.field_id, - content, - } - } + #[pb(index = 4)] + pub is_utc: bool, } #[derive(Clone, Serialize, Deserialize)] -pub struct DateCellChangesetPB { +pub struct DateCellChangeset { pub date: Option, pub time: Option, + pub is_utc: bool, } -impl DateCellChangesetPB { +impl DateCellChangeset { pub fn date_timestamp(&self) -> Option { if let Some(date) = &self.date { match date.parse::() { @@ -87,22 +53,30 @@ impl DateCellChangesetPB { } } -impl FromCellChangeset for DateCellChangesetPB { +impl FromCellChangeset for DateCellChangeset { fn from_changeset(changeset: String) -> FlowyResult where Self: Sized, { - serde_json::from_str::(&changeset).map_err(internal_error) - } -} -pub struct DateTimestamp(i64); -impl AsRef for DateTimestamp { - fn as_ref(&self) -> &i64 { - &self.0 + serde_json::from_str::(&changeset).map_err(internal_error) } } +impl ToString for DateCellChangeset { + fn to_string(&self) -> String { + serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) + } +} + +pub struct DateTimestamp(Option); + impl std::convert::From for i64 { + fn from(timestamp: DateTimestamp) -> Self { + timestamp.0.unwrap_or(0) + } +} + +impl std::convert::From for Option { fn from(timestamp: DateTimestamp) -> Self { timestamp.0 } @@ -113,7 +87,7 @@ impl FromCellString for DateTimestamp { where Self: Sized, { - let num = s.parse::().unwrap_or(0); + let num = s.parse::().ok(); Ok(DateTimestamp(num)) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs index 395f2c9104..ff0c344957 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod date_filter; mod date_tests; mod date_type_option; mod date_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs index 4b2bcc1ecd..8136fb57c5 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/mod.rs @@ -1,5 +1,6 @@ #![allow(clippy::module_inception)] mod format; +mod number_filter; mod number_tests; mod number_type_option; mod number_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs similarity index 52% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs index 45ae0ac464..2fdf4bab97 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/number_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_filter.rs @@ -1,38 +1,44 @@ -use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB}; -use crate::services::cell::{AnyCellData, CellFilterOperation}; +use crate::entities::{NumberFilterCondition, NumberFilterPB}; +use crate::services::cell::{CellFilterOperation, TypeCellData}; use crate::services::field::{NumberCellData, NumberTypeOptionPB}; use flowy_error::FlowyResult; use rust_decimal::prelude::Zero; use rust_decimal::Decimal; use std::str::FromStr; -impl NumberFilterConfigurationPB { +impl NumberFilterPB { pub fn is_visible(&self, num_cell_data: &NumberCellData) -> bool { - if self.content.is_none() { - return false; + if self.content.is_empty() { + match self.condition { + NumberFilterCondition::NumberIsEmpty => { + return num_cell_data.is_empty(); + } + NumberFilterCondition::NumberIsNotEmpty => { + return !num_cell_data.is_empty(); + } + _ => {} + } } - - let content = self.content.as_ref().unwrap(); - let zero_decimal = Decimal::zero(); - let cell_decimal = num_cell_data.decimal().as_ref().unwrap_or(&zero_decimal); - match Decimal::from_str(content) { - Ok(decimal) => match self.condition { - NumberFilterCondition::Equal => cell_decimal == &decimal, - NumberFilterCondition::NotEqual => cell_decimal != &decimal, - NumberFilterCondition::GreaterThan => cell_decimal > &decimal, - NumberFilterCondition::LessThan => cell_decimal < &decimal, - NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, - NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, - NumberFilterCondition::NumberIsEmpty => num_cell_data.is_empty(), - NumberFilterCondition::NumberIsNotEmpty => !num_cell_data.is_empty(), - }, - Err(_) => false, + match num_cell_data.decimal().as_ref() { + None => false, + Some(cell_decimal) => { + let decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero()); + match self.condition { + NumberFilterCondition::Equal => cell_decimal == &decimal, + NumberFilterCondition::NotEqual => cell_decimal != &decimal, + NumberFilterCondition::GreaterThan => cell_decimal > &decimal, + NumberFilterCondition::LessThan => cell_decimal < &decimal, + NumberFilterCondition::GreaterThanOrEqualTo => cell_decimal >= &decimal, + NumberFilterCondition::LessThanOrEqualTo => cell_decimal <= &decimal, + _ => true, + } + } } } } -impl CellFilterOperation for NumberTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &NumberFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for NumberTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &NumberFilterPB) -> FlowyResult { if !any_cell_data.is_number() { return Ok(true); } @@ -46,13 +52,13 @@ impl CellFilterOperation for NumberTypeOptionPB { #[cfg(test)] mod tests { - use crate::entities::{NumberFilterCondition, NumberFilterConfigurationPB}; + use crate::entities::{NumberFilterCondition, NumberFilterPB}; use crate::services::field::{NumberCellData, NumberFormat}; #[test] fn number_filter_equal_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::Equal, - content: Some("123".to_owned()), + content: "123".to_owned(), }; for (num_str, visible) in [("123", true), ("1234", false), ("", false)] { @@ -68,9 +74,9 @@ mod tests { } #[test] fn number_filter_greater_than_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::GreaterThan, - content: Some("12".to_owned()), + content: "12".to_owned(), }; for (num_str, visible) in [("123", true), ("10", false), ("30", true), ("", false)] { let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); @@ -80,11 +86,11 @@ mod tests { #[test] fn number_filter_less_than_test() { - let number_filter = NumberFilterConfigurationPB { + let number_filter = NumberFilterPB { condition: NumberFilterCondition::LessThan, - content: Some("100".to_owned()), + content: "100".to_owned(), }; - for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", true)] { + for (num_str, visible) in [("12", true), ("1234", false), ("30", true), ("", false)] { let data = NumberCellData::from_format_str(num_str, true, &NumberFormat::Num).unwrap(); assert_eq!(number_filter.is_visible(&data), visible); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs index ebef1f0cfd..35ab1cce63 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_tests.rs @@ -4,7 +4,7 @@ mod tests { use crate::services::cell::CellDataOperation; use crate::services::field::FieldBuilder; use crate::services::field::{strip_currency_symbol, NumberFormat, NumberTypeOptionPB}; - use flowy_grid_data_model::revision::FieldRevision; + use grid_rev_model::FieldRevision; use strum::IntoEnumIterator; /// Testing when the input is not a number. diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs index 68cb6a1f2e..f4116e9118 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option/number_type_option.rs @@ -1,14 +1,12 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::type_options::number_type_option::format::*; use crate::services::field::{BoxTypeOptionBuilder, NumberCellData, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use rust_decimal::Decimal; @@ -148,7 +146,7 @@ impl CellDataOperation for NumberTypeOptionPB { fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let changeset = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs index 9932566605..fe117d791f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/mod.rs @@ -1,4 +1,5 @@ mod multi_select_type_option; +mod select_filter; mod select_type_option; mod single_select_type_option; mod type_option_transform; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index a7a2e52732..a6068a0a6d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs @@ -1,6 +1,6 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer; use crate::services::field::type_options::util::get_cell_data; use crate::services::field::{ @@ -10,9 +10,7 @@ use crate::services::field::{ use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use serde::{Deserialize, Serialize}; // Multiple select @@ -52,7 +50,7 @@ impl CellDataOperation for MultiSele fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, cell_rev: Option, ) -> Result { let content_changeset = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs new file mode 100644 index 0000000000..70e51b1d52 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_filter.rs @@ -0,0 +1,174 @@ +#![allow(clippy::needless_collect)] + +use crate::entities::{SelectOptionCondition, SelectOptionFilterPB}; +use crate::services::cell::{CellFilterOperation, TypeCellData}; +use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; +use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions}; +use flowy_error::FlowyResult; + +impl SelectOptionFilterPB { + pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool { + let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); + match self.condition { + SelectOptionCondition::OptionIs => { + if self.option_ids.len() != selected_option_ids.len() { + return false; + } + // if selected options equal to filter's options, then the required_options will be empty. + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); + required_options.len() == selected_option_ids.len() + } + SelectOptionCondition::OptionIsNot => { + if self.option_ids.len() != selected_option_ids.len() { + return true; + } + let required_options = self + .option_ids + .iter() + .filter(|id| selected_option_ids.contains(id)) + .collect::>(); + + required_options.len() != selected_option_ids.len() + } + SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), + SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), + } + } +} + +impl CellFilterOperation for MultiSelectTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult { + if !any_cell_data.is_multi_select() { + return Ok(true); + } + + let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into())); + Ok(filter.is_visible(&selected_options)) + } +} + +impl CellFilterOperation for SingleSelectTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &SelectOptionFilterPB) -> FlowyResult { + if !any_cell_data.is_single_select() { + return Ok(true); + } + let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into())); + Ok(filter.is_visible(&selected_options)) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::all)] + use crate::entities::{SelectOptionCondition, SelectOptionFilterPB}; + use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; + + #[test] + fn select_option_filter_is_empty_test() { + let option = SelectOptionPB::new("A"); + let filter = SelectOptionFilterPB { + condition: SelectOptionCondition::OptionIsEmpty, + option_ids: vec![], + }; + + assert_eq!(filter.is_visible(&SelectedSelectOptions { options: vec![] }), true); + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { options: vec![option] }), + false, + ); + } + + #[test] + fn select_option_filter_is_not_empty_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let filter = SelectOptionFilterPB { + condition: SelectOptionCondition::OptionIsNotEmpty, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1] + }), + true + ); + + assert_eq!(filter.is_visible(&SelectedSelectOptions { options: vec![] }), false,); + } + + #[test] + fn select_option_filter_is_not_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); + + let filter = SelectOptionFilterPB { + condition: SelectOptionCondition::OptionIsNot, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone()], + }), + false + ); + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone(), option_3.clone()], + }), + true + ); + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_3.clone()], + }), + true + ); + + assert_eq!(filter.is_visible(&SelectedSelectOptions { options: vec![] }), true); + } + + #[test] + fn select_option_filter_is_test() { + let option_1 = SelectOptionPB::new("A"); + let option_2 = SelectOptionPB::new("B"); + let option_3 = SelectOptionPB::new("C"); + + let filter = SelectOptionFilterPB { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![option_1.id.clone(), option_2.id.clone()], + }; + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone()], + }), + true + ); + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_2.clone(), option_3.clone()], + }), + false + ); + + assert_eq!( + filter.is_visible(&SelectedSelectOptions { + options: vec![option_1.clone(), option_3.clone()], + }), + false + ); + + assert_eq!(filter.is_visible(&SelectedSelectOptions { options: vec![] }), false); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs index 06244239b6..b38973fe3f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_type_option.rs @@ -1,4 +1,5 @@ -use crate::entities::{CellChangesetPB, FieldType, GridCellIdPB, GridCellIdParams}; +use crate::entities::parser::NotEmptyStr; +use crate::entities::{CellChangesetPB, CellPathPB, CellPathParams, FieldType}; use crate::services::cell::{ CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDisplayable, FromCellChangeset, FromCellString, }; @@ -7,8 +8,7 @@ use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::{internal_error, ErrorCode, FlowyResult}; -use flowy_grid_data_model::parser::NotEmptyStr; -use flowy_grid_data_model::revision::{FieldRevision, TypeOptionDataSerializer}; +use grid_rev_model::{FieldRevision, TypeOptionDataSerializer}; use nanoid::nanoid; use serde::{Deserialize, Serialize}; @@ -101,7 +101,7 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync { } fn create_option(&self, name: &str) -> SelectOptionPB { - let color = select_option_color_from_index(self.options().len()); + let color = new_select_option_color(self.options()); SelectOptionPB::with_color(name, color) } @@ -215,8 +215,20 @@ pub fn select_type_option_from_field_rev( } } -pub fn select_option_color_from_index(index: usize) -> SelectOptionColorPB { - match index % 8 { +pub fn new_select_option_color(options: &Vec) -> SelectOptionColorPB { + let mut freq: Vec = vec![0; 9]; + + for option in options { + freq[option.color.to_owned() as usize] += 1; + } + + match freq + .into_iter() + .enumerate() + .min_by_key(|(_, v)| *v) + .map(|(idx, _val)| idx) + .unwrap() + { 0 => SelectOptionColorPB::Purple, 1 => SelectOptionColorPB::Pink, 2 => SelectOptionColorPB::LightPink, @@ -341,9 +353,9 @@ impl CellBytesParser for SelectOptionCellDataParser { } #[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionCellChangesetPayloadPB { +pub struct SelectOptionCellChangesetPB { #[pb(index = 1)] - pub cell_identifier: GridCellIdPB, + pub cell_identifier: CellPathPB, #[pb(index = 2)] pub insert_option_ids: Vec, @@ -353,7 +365,7 @@ pub struct SelectOptionCellChangesetPayloadPB { } pub struct SelectOptionCellChangesetParams { - pub cell_identifier: GridCellIdParams, + pub cell_identifier: CellPathParams, pub insert_option_ids: Vec, pub delete_option_ids: Vec, } @@ -374,11 +386,11 @@ impl std::convert::From for CellChangesetPB { } } -impl TryInto for SelectOptionCellChangesetPayloadPB { +impl TryInto for SelectOptionCellChangesetPB { type Error = ErrorCode; fn try_into(self) -> Result { - let cell_identifier: GridCellIdParams = self.cell_identifier.try_into()?; + let cell_identifier: CellPathParams = self.cell_identifier.try_into()?; let insert_option_ids = self .insert_option_ids .into_iter() @@ -473,12 +485,12 @@ pub struct SelectOptionCellDataPB { pub select_options: Vec, } -/// [SelectOptionChangesetPayloadPB] describes the changes of a FieldTypeOptionData. For the moment, +/// [SelectOptionChangesetPB] describes the changes of a FieldTypeOptionData. For the moment, /// it is used by [MultiSelectTypeOptionPB] and [SingleSelectTypeOptionPB]. #[derive(Clone, Debug, Default, ProtoBuf)] -pub struct SelectOptionChangesetPayloadPB { +pub struct SelectOptionChangesetPB { #[pb(index = 1)] - pub cell_identifier: GridCellIdPB, + pub cell_identifier: CellPathPB, #[pb(index = 2)] pub insert_options: Vec, @@ -491,13 +503,13 @@ pub struct SelectOptionChangesetPayloadPB { } pub struct SelectOptionChangeset { - pub cell_identifier: GridCellIdParams, + pub cell_identifier: CellPathParams, pub insert_options: Vec, pub update_options: Vec, pub delete_options: Vec, } -impl TryInto for SelectOptionChangesetPayloadPB { +impl TryInto for SelectOptionChangesetPB { type Error = ErrorCode; fn try_into(self) -> Result { diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs index eb164a42ef..d36c09c766 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs @@ -1,6 +1,6 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use crate::services::field::{ @@ -9,9 +9,7 @@ use crate::services::field::{ use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use serde::{Deserialize, Serialize}; // Single select @@ -51,7 +49,7 @@ impl CellDataOperation for SingleSel fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let content_changeset = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/type_option_transform.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/type_option_transform.rs index b829fa148b..cc4dec239b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/type_option_transform.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/type_option_transform.rs @@ -4,7 +4,7 @@ use crate::services::field::{ SelectOptionColorPB, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, CHECK, UNCHECK, }; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::FieldRevision; +use grid_rev_model::FieldRevision; /// Handles how to transform the cell data when switching between different field types pub struct SelectOptionTypeOptionTransformer(); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs index b6bf9a1a1b..9537ee8f33 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod text_filter; mod text_tests; mod text_type_option; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs similarity index 55% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs index 86cb2aadfa..7e6ca21474 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/text_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_filter.rs @@ -1,33 +1,29 @@ -use crate::entities::{TextFilterCondition, TextFilterConfigurationPB}; -use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::entities::{TextFilterCondition, TextFilterPB}; +use crate::services::cell::{CellData, CellFilterOperation, TypeCellData}; use crate::services::field::{RichTextTypeOptionPB, TextCellData}; use flowy_error::FlowyResult; -impl TextFilterConfigurationPB { +impl TextFilterPB { pub fn is_visible>(&self, cell_data: T) -> bool { - let cell_data = cell_data.as_ref(); - let s = cell_data.to_lowercase(); - if let Some(content) = self.content.as_ref() { - match self.condition { - TextFilterCondition::Is => &s == content, - TextFilterCondition::IsNot => &s != content, - TextFilterCondition::Contains => s.contains(content), - TextFilterCondition::DoesNotContain => !s.contains(content), - TextFilterCondition::StartsWith => s.starts_with(content), - TextFilterCondition::EndsWith => s.ends_with(content), - TextFilterCondition::TextIsEmpty => s.is_empty(), - TextFilterCondition::TextIsNotEmpty => !s.is_empty(), - } - } else { - false + let cell_data = cell_data.as_ref().to_lowercase(); + let content = &self.content.to_lowercase(); + match self.condition { + TextFilterCondition::Is => &cell_data == content, + TextFilterCondition::IsNot => &cell_data != content, + TextFilterCondition::Contains => cell_data.contains(content), + TextFilterCondition::DoesNotContain => !cell_data.contains(content), + TextFilterCondition::StartsWith => cell_data.starts_with(content), + TextFilterCondition::EndsWith => cell_data.ends_with(content), + TextFilterCondition::TextIsEmpty => cell_data.is_empty(), + TextFilterCondition::TextIsNotEmpty => !cell_data.is_empty(), } } } -impl CellFilterOperation for RichTextTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for RichTextTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &TextFilterPB) -> FlowyResult { if !any_cell_data.is_text() { - return Ok(true); + return Ok(false); } let cell_data: CellData = any_cell_data.into(); @@ -38,13 +34,13 @@ impl CellFilterOperation for RichTextTypeOptionPB { #[cfg(test)] mod tests { #![allow(clippy::all)] - use crate::entities::{TextFilterCondition, TextFilterConfigurationPB}; + use crate::entities::{TextFilterCondition, TextFilterPB}; #[test] fn text_filter_equal_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::Is, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert!(text_filter.is_visible("AppFlowy")); @@ -54,9 +50,9 @@ mod tests { } #[test] fn text_filter_start_with_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::StartsWith, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("AppFlowy.io"), true); @@ -66,9 +62,9 @@ mod tests { #[test] fn text_filter_end_with_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::EndsWith, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); @@ -77,9 +73,9 @@ mod tests { } #[test] fn text_filter_empty_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::TextIsEmpty, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible(""), true); @@ -87,9 +83,9 @@ mod tests { } #[test] fn text_filter_contain_test() { - let text_filter = TextFilterConfigurationPB { + let text_filter = TextFilterPB { condition: TextFilterCondition::Contains, - content: Some("appflowy".to_owned()), + content: "appflowy".to_owned(), }; assert_eq!(text_filter.is_visible("https://github.com/appflowy"), true); diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs index 79bd48a2b0..d9d4216f8b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/text_type_option/text_type_option.rs @@ -1,16 +1,14 @@ use crate::entities::FieldType; use crate::impl_type_option; use crate::services::cell::{ - decode_cell_data_to_string, CellBytes, CellBytesParser, CellData, CellDataChangeset, CellDataIsEmpty, + decode_cell_data_to_string, AnyCellChangeset, CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDataOperation, CellDisplayable, FromCellString, }; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder}; use bytes::Bytes; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use serde::{Deserialize, Serialize}; #[derive(Default)] @@ -85,7 +83,7 @@ impl CellDataOperation for RichTextTypeOptionPB { fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let data = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs index 8f6cb884df..bbe195c628 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/mod.rs @@ -1,4 +1,5 @@ #![allow(clippy::module_inception)] +mod url_filter; mod url_tests; mod url_type_option; mod url_type_option_entities; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs similarity index 53% rename from frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs rename to frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs index 4f0a7b93cd..ae2baa5cb8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/url_filter.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_filter.rs @@ -1,10 +1,10 @@ -use crate::entities::TextFilterConfigurationPB; -use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; +use crate::entities::TextFilterPB; +use crate::services::cell::{CellData, CellFilterOperation, TypeCellData}; use crate::services::field::{TextCellData, URLTypeOptionPB}; use flowy_error::FlowyResult; -impl CellFilterOperation for URLTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &TextFilterConfigurationPB) -> FlowyResult { +impl CellFilterOperation for URLTypeOptionPB { + fn apply_filter(&self, any_cell_data: TypeCellData, filter: &TextFilterPB) -> FlowyResult { if !any_cell_data.is_url() { return Ok(true); } diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs index 97b8275287..b66badcf5b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_tests.rs @@ -4,7 +4,7 @@ mod tests { use crate::services::cell::{CellData, CellDataOperation}; use crate::services::field::{FieldBuilder, URLCellDataParser}; use crate::services::field::{URLCellDataPB, URLTypeOptionPB}; - use flowy_grid_data_model::revision::FieldRevision; + use grid_rev_model::FieldRevision; /// The expected_str will equal to the input string, but the expected_url will be empty if there's no /// http url in the input string. diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs index 760c480a05..a91d44454b 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/url_type_option/url_type_option.rs @@ -1,14 +1,12 @@ use crate::entities::FieldType; use crate::impl_type_option; -use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable}; +use crate::services::cell::{AnyCellChangeset, CellBytes, CellData, CellDataOperation, CellDisplayable}; use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder, URLCellDataPB}; use bytes::Bytes; use fancy_regex::Regex; use flowy_derive::ProtoBuf; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer, -}; +use grid_rev_model::{CellRevision, FieldRevision, TypeOptionDataDeserializer, TypeOptionDataSerializer}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; @@ -75,7 +73,7 @@ impl CellDataOperation for URLTypeOptionPB { fn apply_changeset( &self, - changeset: CellDataChangeset, + changeset: AnyCellChangeset, _cell_rev: Option, ) -> Result { let content = changeset.try_into_inner()?; diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs index 549f502bb0..78ecc4b7d9 100644 --- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/util/cell_data_util.rs @@ -1,9 +1,9 @@ -use crate::services::cell::AnyCellData; -use flowy_grid_data_model::revision::CellRevision; +use crate::services::cell::TypeCellData; +use grid_rev_model::CellRevision; use std::str::FromStr; pub fn get_cell_data(cell_rev: &CellRevision) -> String { - match AnyCellData::from_str(&cell_rev.data) { + match TypeCellData::from_str(&cell_rev.data) { Ok(type_option) => type_option.data, Err(_) => String::new(), } diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs new file mode 100644 index 0000000000..6d0f81d242 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/cache.rs @@ -0,0 +1,109 @@ +use crate::entities::{CheckboxFilterPB, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB}; +use crate::services::filter::FilterType; +use std::collections::HashMap; + +#[derive(Default, Debug)] +pub(crate) struct FilterMap { + pub(crate) text_filter: HashMap, + pub(crate) url_filter: HashMap, + pub(crate) number_filter: HashMap, + pub(crate) date_filter: HashMap, + pub(crate) select_option_filter: HashMap, + pub(crate) checkbox_filter: HashMap, +} + +impl FilterMap { + pub(crate) fn new() -> Self { + Self::default() + } + + pub(crate) fn has_filter(&self, filter_type: &FilterType) -> bool { + match filter_type.field_type { + FieldType::RichText => self.text_filter.get(filter_type).is_some(), + FieldType::Number => self.number_filter.get(filter_type).is_some(), + FieldType::DateTime => self.date_filter.get(filter_type).is_some(), + FieldType::SingleSelect => self.select_option_filter.get(filter_type).is_some(), + FieldType::MultiSelect => self.select_option_filter.get(filter_type).is_some(), + FieldType::Checkbox => self.checkbox_filter.get(filter_type).is_some(), + FieldType::URL => self.url_filter.get(filter_type).is_some(), + } + } + + pub(crate) fn is_empty(&self) -> bool { + if !self.text_filter.is_empty() { + return false; + } + + if !self.url_filter.is_empty() { + return false; + } + + if !self.number_filter.is_empty() { + return false; + } + + if !self.number_filter.is_empty() { + return false; + } + + if !self.date_filter.is_empty() { + return false; + } + + if !self.select_option_filter.is_empty() { + return false; + } + + if !self.checkbox_filter.is_empty() { + return false; + } + true + } + + pub(crate) fn remove(&mut self, filter_id: &FilterType) { + let _ = match filter_id.field_type { + FieldType::RichText => { + let _ = self.text_filter.remove(filter_id); + } + FieldType::Number => { + let _ = self.number_filter.remove(filter_id); + } + FieldType::DateTime => { + let _ = self.date_filter.remove(filter_id); + } + FieldType::SingleSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::MultiSelect => { + let _ = self.select_option_filter.remove(filter_id); + } + FieldType::Checkbox => { + let _ = self.checkbox_filter.remove(filter_id); + } + FieldType::URL => { + let _ = self.url_filter.remove(filter_id); + } + }; + } +} + +/// Refresh the filter according to the field id. +#[derive(Default)] +pub(crate) struct FilterResult { + pub(crate) visible_by_filter_id: HashMap, +} + +impl FilterResult { + pub(crate) fn is_visible(&self) -> bool { + if self.visible_by_filter_id.is_empty() { + return false; + } + + for visible in self.visible_by_filter_id.values() { + if visible == &false { + return false; + } + } + true + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs b/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs new file mode 100644 index 0000000000..c3b9b31d6d --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/controller.rs @@ -0,0 +1,325 @@ +use crate::entities::filter_entities::*; + +use crate::entities::FieldType; +use crate::services::cell::{CellFilterOperation, TypeCellData}; +use crate::services::field::*; +use crate::services::filter::{FilterChangeset, FilterMap, FilterResult, FilterResultNotification, FilterType}; +use crate::services::row::GridBlock; +use crate::services::view_editor::{GridViewChanged, GridViewChangedNotifier}; +use flowy_error::FlowyResult; +use flowy_task::{QualityOfService, Task, TaskContent, TaskDispatcher}; +use grid_rev_model::{CellRevision, FieldId, FieldRevision, FilterRevision, RowRevision}; +use lib_infra::future::Fut; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +type RowId = String; +pub trait FilterDelegate: Send + Sync + 'static { + fn get_filter_rev(&self, filter_id: FilterType) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_blocks(&self) -> Fut>; +} + +pub struct FilterController { + view_id: String, + handler_id: String, + delegate: Box, + filter_map: FilterMap, + result_by_row_id: HashMap, + task_scheduler: Arc>, + notifier: GridViewChangedNotifier, +} + +impl FilterController { + pub async fn new( + view_id: &str, + handler_id: &str, + delegate: T, + task_scheduler: Arc>, + filter_revs: Vec>, + notifier: GridViewChangedNotifier, + ) -> Self + where + T: FilterDelegate, + { + let mut this = Self { + view_id: view_id.to_string(), + handler_id: handler_id.to_string(), + delegate: Box::new(delegate), + filter_map: FilterMap::new(), + result_by_row_id: HashMap::default(), + task_scheduler, + notifier, + }; + this.load_filters(filter_revs).await; + this + } + + pub async fn close(&self) { + self.task_scheduler.write().await.unregister_handler(&self.handler_id); + } + + #[tracing::instrument(name = "schedule_filter_task", level = "trace", skip(self))] + async fn gen_task(&mut self, predicate: &str) { + let task_id = self.task_scheduler.read().await.next_task_id(); + let task = Task::new( + &self.handler_id, + task_id, + TaskContent::Text(predicate.to_owned()), + QualityOfService::UserInteractive, + ); + self.task_scheduler.write().await.add_task(task); + } + + pub async fn filter_row_revs(&mut self, row_revs: &mut Vec>) { + if self.filter_map.is_empty() { + return; + } + let field_rev_by_field_id = self.get_filter_revs_map().await; + row_revs.iter().for_each(|row_rev| { + let _ = filter_row( + row_rev, + &self.filter_map, + &mut self.result_by_row_id, + &field_rev_by_field_id, + ); + }); + + row_revs.retain(|row_rev| { + self.result_by_row_id + .get(&row_rev.id) + .map(|result| result.is_visible()) + .unwrap_or(false) + }); + } + + async fn get_filter_revs_map(&self) -> HashMap> { + self.delegate + .get_field_revs(None) + .await + .into_iter() + .map(|field_rev| (field_rev.id.clone(), field_rev)) + .collect::>>() + } + + #[tracing::instrument(name = "receive_task_result", level = "trace", skip_all, fields(filter_result), err)] + pub async fn process(&mut self, _predicate: &str) -> FlowyResult<()> { + let field_rev_by_field_id = self.get_filter_revs_map().await; + for block in self.delegate.get_blocks().await.into_iter() { + // The row_ids contains the row that its visibility was changed. + let mut visible_rows = vec![]; + let mut invisible_rows = vec![]; + + for row_rev in &block.row_revs { + let (row_id, is_visible) = filter_row( + row_rev, + &self.filter_map, + &mut self.result_by_row_id, + &field_rev_by_field_id, + ); + if is_visible { + visible_rows.push(row_id) + } else { + invisible_rows.push(row_id); + } + } + + let notification = FilterResultNotification { + view_id: self.view_id.clone(), + block_id: block.block_id, + invisible_rows, + visible_rows, + }; + tracing::Span::current().record("filter_result", &format!("{:?}", ¬ification).as_str()); + let _ = self + .notifier + .send(GridViewChanged::DidReceiveFilterResult(notification)); + } + + Ok(()) + } + + pub async fn apply_changeset(&mut self, changeset: FilterChangeset) { + if let Some(filter_id) = &changeset.insert_filter { + let filter_revs = self.delegate.get_filter_rev(filter_id.clone()).await; + let _ = self.load_filters(filter_revs).await; + } + + if let Some(filter_id) = &changeset.delete_filter { + self.filter_map.remove(filter_id); + } + + self.gen_task("").await; + } + + #[tracing::instrument(level = "trace", skip_all)] + async fn load_filters(&mut self, filter_revs: Vec>) { + for filter_rev in filter_revs { + if let Some(field_rev) = self.delegate.get_field_rev(&filter_rev.field_id).await { + let filter_type = FilterType::from(&field_rev); + tracing::trace!("Create filter with type: {:?}", filter_type); + match &filter_type.field_type { + FieldType::RichText => { + let _ = self + .filter_map + .text_filter + .insert(filter_type, TextFilterPB::from(filter_rev.as_ref())); + } + FieldType::Number => { + let _ = self + .filter_map + .number_filter + .insert(filter_type, NumberFilterPB::from(filter_rev.as_ref())); + } + FieldType::DateTime => { + let _ = self + .filter_map + .date_filter + .insert(filter_type, DateFilterPB::from(filter_rev.as_ref())); + } + FieldType::SingleSelect | FieldType::MultiSelect => { + let _ = self + .filter_map + .select_option_filter + .insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref())); + } + FieldType::Checkbox => { + let _ = self + .filter_map + .checkbox_filter + .insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref())); + } + FieldType::URL => { + let _ = self + .filter_map + .url_filter + .insert(filter_type, TextFilterPB::from(filter_rev.as_ref())); + } + } + } + } + } +} + +/// Returns None if there is no change in this row after applying the filter +#[tracing::instrument(level = "trace", skip_all)] +fn filter_row( + row_rev: &Arc, + filter_map: &FilterMap, + result_by_row_id: &mut HashMap, + field_rev_by_field_id: &HashMap>, +) -> (String, bool) { + // Create a filter result cache if it's not exist + let filter_result = result_by_row_id + .entry(row_rev.id.clone()) + .or_insert_with(FilterResult::default); + + // Iterate each cell of the row to check its visibility + for (field_id, field_rev) in field_rev_by_field_id { + let filter_type = FilterType::from(field_rev); + if !filter_map.has_filter(&filter_type) { + // tracing::trace!( + // "Can't find filter for filter type: {:?}. Current filters: {:?}", + // filter_type, + // filter_map + // ); + continue; + } + + let cell_rev = row_rev.cells.get(field_id); + // if the visibility of the cell_rew is changed, which means the visibility of the + // row is changed too. + if let Some(is_visible) = filter_cell(&filter_type, field_rev, filter_map, cell_rev) { + filter_result.visible_by_filter_id.insert(filter_type, is_visible); + return (row_rev.id.clone(), is_visible); + } + } + + (row_rev.id.clone(), true) +} + +// Returns None if there is no change in this cell after applying the filter +// Returns Some if the visibility of the cell is changed + +#[tracing::instrument(level = "trace", skip_all)] +fn filter_cell( + filter_id: &FilterType, + field_rev: &Arc, + filter_map: &FilterMap, + cell_rev: Option<&CellRevision>, +) -> Option { + let any_cell_data = match cell_rev { + None => TypeCellData::from_field_type(&filter_id.field_type), + Some(cell_rev) => match TypeCellData::try_from(cell_rev) { + Ok(cell_data) => cell_data, + Err(err) => { + tracing::error!("Deserialize TypeCellData failed: {}", err); + TypeCellData::from_field_type(&filter_id.field_type) + } + }, + }; + tracing::trace!("filter cell: {:?}", any_cell_data); + + let is_visible = match &filter_id.field_type { + FieldType::RichText => filter_map.text_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::Number => filter_map.number_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::DateTime => filter_map.date_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::SingleSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::MultiSelect => filter_map.select_option_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::Checkbox => filter_map.checkbox_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + FieldType::URL => filter_map.url_filter.get(filter_id).and_then(|filter| { + Some( + field_rev + .get_type_option::(field_rev.ty)? + .apply_filter(any_cell_data, filter) + .ok(), + ) + }), + }?; + + is_visible +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/entities.rs b/frontend/rust-lib/flowy-grid/src/services/filter/entities.rs new file mode 100644 index 0000000000..291c5fe544 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/entities.rs @@ -0,0 +1,87 @@ +use crate::entities::{CreateFilterParams, DeleteFilterParams, FieldType, GridSettingChangesetParams}; +use grid_rev_model::{FieldRevision, FieldTypeRevision}; +use std::sync::Arc; + +pub struct FilterChangeset { + pub(crate) insert_filter: Option, + pub(crate) delete_filter: Option, +} + +impl FilterChangeset { + pub fn from_insert(filter_id: FilterType) -> Self { + Self { + insert_filter: Some(filter_id), + delete_filter: None, + } + } + + pub fn from_delete(filter_id: FilterType) -> Self { + Self { + insert_filter: None, + delete_filter: Some(filter_id), + } + } +} + +impl std::convert::From<&GridSettingChangesetParams> for FilterChangeset { + fn from(params: &GridSettingChangesetParams) -> Self { + let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterType { + field_id: insert_filter_params.field_id.clone(), + field_type: insert_filter_params.field_type_rev.into(), + }); + + let delete_filter = params + .delete_filter + .as_ref() + .map(|delete_filter_params| delete_filter_params.filter_type.clone()); + FilterChangeset { + insert_filter, + delete_filter, + } + } +} + +#[derive(Hash, Eq, PartialEq, Debug, Clone)] +pub struct FilterType { + pub field_id: String, + pub field_type: FieldType, +} + +impl FilterType { + pub fn field_type_rev(&self) -> FieldTypeRevision { + self.field_type.clone().into() + } +} + +impl std::convert::From<&Arc> for FilterType { + fn from(rev: &Arc) -> Self { + Self { + field_id: rev.id.clone(), + field_type: rev.ty.into(), + } + } +} + +impl std::convert::From<&CreateFilterParams> for FilterType { + fn from(params: &CreateFilterParams) -> Self { + let field_type: FieldType = params.field_type_rev.into(); + Self { + field_id: params.field_id.clone(), + field_type, + } + } +} + +impl std::convert::From<&DeleteFilterParams> for FilterType { + fn from(params: &DeleteFilterParams) -> Self { + params.filter_type.clone() + } +} + +#[derive(Clone, Debug)] +pub struct FilterResultNotification { + pub view_id: String, + pub block_id: String, + pub visible_rows: Vec, + pub invisible_rows: Vec, +} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs deleted file mode 100644 index cad2998a5f..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs +++ /dev/null @@ -1,171 +0,0 @@ -use crate::entities::{ - CheckboxFilterConfigurationPB, DateFilterConfigurationPB, FieldType, NumberFilterConfigurationPB, - SelectOptionFilterConfigurationPB, TextFilterConfigurationPB, -}; -use dashmap::DashMap; -use flowy_grid_data_model::revision::{FieldRevision, FilterConfigurationRevision, RowRevision}; -use flowy_sync::client_grid::GridRevisionPad; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -type RowId = String; - -#[derive(Default)] -pub(crate) struct FilterResultCache { - // key: row id - inner: DashMap, -} - -impl FilterResultCache { - pub fn new() -> Arc { - let this = Self::default(); - Arc::new(this) - } -} - -impl std::ops::Deref for FilterResultCache { - type Target = DashMap; - - fn deref(&self) -> &Self::Target { - &self.inner - } -} - -#[derive(Default)] -pub(crate) struct FilterResult { - #[allow(dead_code)] - pub(crate) row_index: i32, - pub(crate) visible_by_field_id: HashMap, -} - -impl FilterResult { - pub(crate) fn new(index: i32, _row_rev: &RowRevision) -> Self { - Self { - row_index: index, - visible_by_field_id: HashMap::new(), - } - } - - pub(crate) fn is_visible(&self) -> bool { - for visible in self.visible_by_field_id.values() { - if visible == &false { - return false; - } - } - true - } -} - -#[derive(Default)] -pub(crate) struct FilterCache { - pub(crate) text_filter: DashMap, - pub(crate) url_filter: DashMap, - pub(crate) number_filter: DashMap, - pub(crate) date_filter: DashMap, - pub(crate) select_option_filter: DashMap, - pub(crate) checkbox_filter: DashMap, -} - -impl FilterCache { - pub(crate) async fn from_grid_pad(grid_pad: &Arc>) -> Arc { - let this = Arc::new(Self::default()); - let _ = refresh_filter_cache(this.clone(), None, grid_pad).await; - this - } - - #[allow(dead_code)] - pub(crate) fn remove(&self, filter_id: &FilterId) { - let _ = match filter_id.field_type { - FieldType::RichText => { - let _ = self.text_filter.remove(filter_id); - } - FieldType::Number => { - let _ = self.number_filter.remove(filter_id); - } - FieldType::DateTime => { - let _ = self.date_filter.remove(filter_id); - } - FieldType::SingleSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::MultiSelect => { - let _ = self.select_option_filter.remove(filter_id); - } - FieldType::Checkbox => { - let _ = self.checkbox_filter.remove(filter_id); - } - FieldType::URL => { - let _ = self.url_filter.remove(filter_id); - } - }; - } -} - -/// Refresh the filter according to the field id. -pub(crate) async fn refresh_filter_cache( - cache: Arc, - _field_ids: Option>, - grid_pad: &Arc>, -) { - let grid_pad = grid_pad.read().await; - // let filters_revs = grid_pad.get_filters(field_ids).unwrap_or_default(); - // TODO nathan - let filter_revs: Vec> = vec![]; - - for filter_rev in filter_revs { - match grid_pad.get_field_rev(&filter_rev.field_id) { - None => {} - Some((_, field_rev)) => { - let filter_id = FilterId::from(field_rev); - let field_type: FieldType = field_rev.ty.into(); - match &field_type { - FieldType::RichText => { - let _ = cache - .text_filter - .insert(filter_id, TextFilterConfigurationPB::from(filter_rev)); - } - FieldType::Number => { - let _ = cache - .number_filter - .insert(filter_id, NumberFilterConfigurationPB::from(filter_rev)); - } - FieldType::DateTime => { - let _ = cache - .date_filter - .insert(filter_id, DateFilterConfigurationPB::from(filter_rev)); - } - FieldType::SingleSelect | FieldType::MultiSelect => { - let _ = cache - .select_option_filter - .insert(filter_id, SelectOptionFilterConfigurationPB::from(filter_rev)); - } - FieldType::Checkbox => { - let _ = cache - .checkbox_filter - .insert(filter_id, CheckboxFilterConfigurationPB::from(filter_rev)); - } - FieldType::URL => { - let _ = cache - .url_filter - .insert(filter_id, TextFilterConfigurationPB::from(filter_rev)); - } - } - } - } - } -} -#[derive(Hash, Eq, PartialEq)] -pub(crate) struct FilterId { - pub(crate) field_id: String, - pub(crate) field_type: FieldType, -} - -impl std::convert::From<&Arc> for FilterId { - fn from(rev: &Arc) -> Self { - Self { - field_id: rev.id.clone(), - field_type: rev.ty.into(), - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs deleted file mode 100644 index 173dc8ffea..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs +++ /dev/null @@ -1,294 +0,0 @@ -#![allow(clippy::all)] -#![allow(unused_attributes)] -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_results)] -use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{FieldType, GridBlockChangesetPB, GridSettingChangesetParams}; -use crate::services::block_manager::GridBlockManager; -use crate::services::cell::{AnyCellData, CellFilterOperation}; -use crate::services::field::{ - CheckboxTypeOptionPB, DateTypeOptionPB, MultiSelectTypeOptionPB, NumberTypeOptionPB, RichTextTypeOptionPB, - SingleSelectTypeOptionPB, URLTypeOptionPB, -}; -use crate::services::filter::filter_cache::{ - refresh_filter_cache, FilterCache, FilterId, FilterResult, FilterResultCache, -}; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::row::GridBlockSnapshot; -use crate::services::tasks::{FilterTaskContext, Task, TaskContent}; -use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{CellRevision, FieldId, FieldRevision, RowRevision}; -use flowy_sync::client_grid::GridRevisionPad; -use rayon::prelude::*; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -pub(crate) struct GridFilterService { - #[allow(dead_code)] - scheduler: Arc, - grid_pad: Arc>, - #[allow(dead_code)] - block_manager: Arc, - filter_cache: Arc, - filter_result_cache: Arc, -} -impl GridFilterService { - pub async fn new( - grid_pad: Arc>, - block_manager: Arc, - scheduler: S, - ) -> Self { - let scheduler = Arc::new(scheduler); - let filter_cache = FilterCache::from_grid_pad(&grid_pad).await; - let filter_result_cache = FilterResultCache::new(); - Self { - grid_pad, - block_manager, - scheduler, - filter_cache, - filter_result_cache, - } - } - - pub async fn process(&self, task_context: FilterTaskContext) -> FlowyResult<()> { - let field_revs = self - .grid_pad - .read() - .await - .get_field_revs(None)? - .into_iter() - .map(|field_rev| (field_rev.id.clone(), field_rev)) - .collect::>>(); - - let mut changesets = vec![]; - for (index, block) in task_context.blocks.into_iter().enumerate() { - // The row_ids contains the row that its visibility was changed. - let row_ids = block - .row_revs - .par_iter() - .flat_map(|row_rev| { - let filter_result_cache = self.filter_result_cache.clone(); - let filter_cache = self.filter_cache.clone(); - filter_row(index, row_rev, filter_cache, filter_result_cache, &field_revs) - }) - .collect::>(); - - let mut visible_rows = vec![]; - let mut hide_rows = vec![]; - - // Query the filter result from the cache - for row_id in row_ids { - if self - .filter_result_cache - .get(&row_id) - .map(|result| result.is_visible()) - .unwrap_or(false) - { - visible_rows.push(row_id); - } else { - hide_rows.push(row_id); - } - } - - let changeset = GridBlockChangesetPB { - block_id: block.block_id, - hide_rows, - visible_rows, - ..Default::default() - }; - - // Save the changeset for each block - changesets.push(changeset); - } - - self.notify(changesets).await; - Ok(()) - } - - pub async fn apply_changeset(&self, changeset: GridFilterChangeset) { - if !changeset.is_changed() { - return; - } - - if let Some(filter_id) = &changeset.insert_filter { - let field_ids = Some(vec![filter_id.field_id.clone()]); - refresh_filter_cache(self.filter_cache.clone(), field_ids, &self.grid_pad).await; - } - - if let Some(filter_id) = &changeset.delete_filter { - self.filter_cache.remove(filter_id); - } - - if let Ok(blocks) = self.block_manager.get_block_snapshots(None).await { - let _task = self.gen_task(blocks).await; - // let _ = self.scheduler.register_task(task).await; - } - } - - async fn gen_task(&self, blocks: Vec) -> Task { - let task_id = self.scheduler.gen_task_id().await; - let handler_id = self.grid_pad.read().await.grid_id(); - - let context = FilterTaskContext { blocks }; - Task::new(&handler_id, task_id, TaskContent::Filter(context)) - } - - async fn notify(&self, changesets: Vec) { - let grid_id = self.grid_pad.read().await.grid_id(); - for changeset in changesets { - send_dart_notification(&grid_id, GridNotification::DidUpdateGridBlock) - .payload(changeset) - .send(); - } - } -} - -// Return None if there is no change in this row after applying the filter -fn filter_row( - index: usize, - row_rev: &Arc, - filter_cache: Arc, - filter_result_cache: Arc, - field_revs: &HashMap>, -) -> Option { - let mut result = filter_result_cache - .entry(row_rev.id.clone()) - .or_insert(FilterResult::new(index as i32, row_rev)); - - for (field_id, cell_rev) in row_rev.cells.iter() { - match filter_cell(field_revs, result.value_mut(), &filter_cache, field_id, cell_rev) { - None => {} - Some(_) => { - return Some(row_rev.id.clone()); - } - } - } - None -} - -// Return None if there is no change in this cell after applying the filter -fn filter_cell( - field_revs: &HashMap>, - filter_result: &mut FilterResult, - filter_cache: &Arc, - field_id: &str, - cell_rev: &CellRevision, -) -> Option<()> { - let field_rev = field_revs.get(field_id)?; - let field_type = FieldType::from(field_rev.ty); - let field_type_rev = field_type.clone().into(); - let filter_id = FilterId { - field_id: field_id.to_owned(), - field_type, - }; - let any_cell_data = AnyCellData::try_from(cell_rev).ok()?; - let is_visible = match &filter_id.field_type { - FieldType::RichText => filter_cache.text_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::Number => filter_cache.number_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::DateTime => filter_cache.date_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::SingleSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::MultiSelect => filter_cache.select_option_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::Checkbox => filter_cache.checkbox_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - FieldType::URL => filter_cache.url_filter.get(&filter_id).and_then(|filter| { - Some( - field_rev - .get_type_option::(field_type_rev)? - .apply_filter(any_cell_data, filter.value()) - .ok(), - ) - }), - }?; - - let is_visible = !is_visible.unwrap_or(true); - match filter_result.visible_by_field_id.get(&filter_id) { - None => { - if is_visible { - None - } else { - filter_result.visible_by_field_id.insert(filter_id, is_visible); - Some(()) - } - } - Some(old_is_visible) => { - if old_is_visible != &is_visible { - filter_result.visible_by_field_id.insert(filter_id, is_visible); - Some(()) - } else { - None - } - } - } -} - -pub struct GridFilterChangeset { - insert_filter: Option, - delete_filter: Option, -} - -impl GridFilterChangeset { - fn is_changed(&self) -> bool { - self.insert_filter.is_some() || self.delete_filter.is_some() - } -} - -impl std::convert::From<&GridSettingChangesetParams> for GridFilterChangeset { - fn from(params: &GridSettingChangesetParams) -> Self { - let insert_filter = params.insert_filter.as_ref().map(|insert_filter_params| FilterId { - field_id: insert_filter_params.field_id.clone(), - field_type: insert_filter_params.field_type_rev.into(), - }); - - let delete_filter = params.delete_filter.as_ref().map(|delete_filter_params| FilterId { - field_id: delete_filter_params.filter_id.clone(), - field_type: delete_filter_params.field_type_rev.into(), - }); - GridFilterChangeset { - insert_filter, - delete_filter, - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs deleted file mode 100644 index 18d968d2a4..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/date_filter.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::entities::{DateFilterCondition, DateFilterConfigurationPB}; -use crate::services::cell::{AnyCellData, CellData, CellFilterOperation}; -use crate::services::field::{DateTimestamp, DateTypeOptionPB}; -use flowy_error::FlowyResult; - -impl DateFilterConfigurationPB { - pub fn is_visible>(&self, cell_timestamp: T) -> bool { - if self.start.is_none() { - return false; - } - let cell_timestamp = cell_timestamp.into(); - let start_timestamp = *self.start.as_ref().unwrap(); - // We assume that the cell_timestamp doesn't contain hours, just day. - match self.condition { - DateFilterCondition::DateIs => cell_timestamp == start_timestamp, - DateFilterCondition::DateBefore => cell_timestamp < start_timestamp, - DateFilterCondition::DateAfter => cell_timestamp > start_timestamp, - DateFilterCondition::DateOnOrBefore => cell_timestamp <= start_timestamp, - DateFilterCondition::DateOnOrAfter => cell_timestamp >= start_timestamp, - DateFilterCondition::DateWithIn => { - if let Some(end_timestamp) = self.end.as_ref() { - cell_timestamp >= start_timestamp && cell_timestamp <= *end_timestamp - } else { - false - } - } - DateFilterCondition::DateIsEmpty => cell_timestamp == 0_i64, - } - } -} - -impl CellFilterOperation for DateTypeOptionPB { - fn apply_filter(&self, any_cell_data: AnyCellData, filter: &DateFilterConfigurationPB) -> FlowyResult { - if !any_cell_data.is_date() { - return Ok(true); - } - let cell_data: CellData = any_cell_data.into(); - let timestamp = cell_data.try_into_inner()?; - Ok(filter.is_visible(timestamp)) - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{DateFilterCondition, DateFilterConfigurationPB}; - - #[test] - fn date_filter_is_test() { - let filter = DateFilterConfigurationPB { - condition: DateFilterCondition::DateIs, - start: Some(123), - end: None, - }; - - for (val, visible) in vec![(123, true), (12, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_before_test() { - let filter = DateFilterConfigurationPB { - condition: DateFilterCondition::DateBefore, - start: Some(123), - end: None, - }; - - for (val, visible) in vec![(123, false), (122, true)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_before_or_on_test() { - let filter = DateFilterConfigurationPB { - condition: DateFilterCondition::DateOnOrBefore, - start: Some(123), - end: None, - }; - - for (val, visible) in vec![(123, true), (122, true)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_after_test() { - let filter = DateFilterConfigurationPB { - condition: DateFilterCondition::DateAfter, - start: Some(123), - end: None, - }; - - for (val, visible) in vec![(1234, true), (122, false), (0, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } - #[test] - fn date_filter_within_test() { - let filter = DateFilterConfigurationPB { - condition: DateFilterCondition::DateWithIn, - start: Some(123), - end: Some(130), - }; - - for (val, visible) in vec![(123, true), (130, true), (132, false)] { - assert_eq!(filter.is_visible(val as i64), visible); - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs deleted file mode 100644 index 6fe93ae58c..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod checkbox_filter; -mod date_filter; -mod number_filter; -mod select_option_filter; -mod text_filter; -mod url_filter; - -pub use checkbox_filter::*; -pub use date_filter::*; -pub use number_filter::*; -pub use select_option_filter::*; -pub use text_filter::*; -pub use url_filter::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs b/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs deleted file mode 100644 index b8f330b72c..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/filter/impls/select_option_filter.rs +++ /dev/null @@ -1,117 +0,0 @@ -#![allow(clippy::needless_collect)] - -use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB}; -use crate::services::cell::{AnyCellData, CellFilterOperation}; -use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB}; -use crate::services::field::{SelectTypeOptionSharedAction, SelectedSelectOptions}; -use flowy_error::FlowyResult; - -impl SelectOptionFilterConfigurationPB { - pub fn is_visible(&self, selected_options: &SelectedSelectOptions) -> bool { - let selected_option_ids: Vec<&String> = selected_options.options.iter().map(|option| &option.id).collect(); - match self.condition { - SelectOptionCondition::OptionIs => { - if self.option_ids.len() != selected_option_ids.len() { - return true; - } - - // if selected options equal to filter's options, then the required_options will be empty. - let required_options = self - .option_ids - .iter() - .filter(|id| !selected_option_ids.contains(id)) - .collect::>(); - - // https://stackoverflow.com/questions/69413164/how-to-fix-this-clippy-warning-needless-collect - !required_options.is_empty() - } - SelectOptionCondition::OptionIsNot => { - for option_id in selected_option_ids { - if self.option_ids.contains(option_id) { - return true; - } - } - false - } - SelectOptionCondition::OptionIsEmpty => selected_option_ids.is_empty(), - SelectOptionCondition::OptionIsNotEmpty => !selected_option_ids.is_empty(), - } - } -} - -impl CellFilterOperation for MultiSelectTypeOptionPB { - fn apply_filter( - &self, - any_cell_data: AnyCellData, - filter: &SelectOptionFilterConfigurationPB, - ) -> FlowyResult { - if !any_cell_data.is_multi_select() { - return Ok(true); - } - - let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into())); - Ok(filter.is_visible(&selected_options)) - } -} - -impl CellFilterOperation for SingleSelectTypeOptionPB { - fn apply_filter( - &self, - any_cell_data: AnyCellData, - filter: &SelectOptionFilterConfigurationPB, - ) -> FlowyResult { - if !any_cell_data.is_single_select() { - return Ok(true); - } - let selected_options = SelectedSelectOptions::from(self.get_selected_options(any_cell_data.into())); - Ok(filter.is_visible(&selected_options)) - } -} - -#[cfg(test)] -mod tests { - #![allow(clippy::all)] - use crate::entities::{SelectOptionCondition, SelectOptionFilterConfigurationPB}; - use crate::services::field::selection_type_option::{SelectOptionPB, SelectedSelectOptions}; - - #[test] - fn select_option_filter_is_test() { - let option_1 = SelectOptionPB::new("A"); - let option_2 = SelectOptionPB::new("B"); - let option_3 = SelectOptionPB::new("C"); - - let filter_1 = SelectOptionFilterConfigurationPB { - condition: SelectOptionCondition::OptionIs, - option_ids: vec![option_1.id.clone(), option_2.id.clone()], - }; - - assert_eq!( - filter_1.is_visible(&SelectedSelectOptions { - options: vec![option_1.clone(), option_2.clone()], - }), - false - ); - - assert_eq!( - filter_1.is_visible(&SelectedSelectOptions { - options: vec![option_1.clone(), option_2.clone(), option_3.clone()], - }), - true - ); - - assert_eq!( - filter_1.is_visible(&SelectedSelectOptions { - options: vec![option_1.clone(), option_3.clone()], - }), - true - ); - - assert_eq!(filter_1.is_visible(&SelectedSelectOptions { options: vec![] }), true); - assert_eq!( - filter_1.is_visible(&SelectedSelectOptions { - options: vec![option_1.clone()], - }), - true, - ); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs index 8a0067ff96..214bcbb8b7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/filter/mod.rs @@ -1,5 +1,9 @@ -mod filter_cache; -mod filter_service; -mod impls; +mod cache; +mod controller; +mod entities; +mod task; -pub(crate) use filter_service::*; +pub(crate) use cache::*; +pub use controller::*; +pub use entities::*; +pub(crate) use task::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/task.rs b/frontend/rust-lib/flowy-grid/src/services/filter/task.rs new file mode 100644 index 0000000000..f43694a267 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/filter/task.rs @@ -0,0 +1,40 @@ +use crate::services::filter::FilterController; +use flowy_task::{TaskContent, TaskHandler}; +use lib_infra::future::BoxResultFuture; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub struct FilterTaskHandler { + handler_id: String, + filter_controller: Arc>, +} + +impl FilterTaskHandler { + pub fn new(handler_id: String, filter_controller: Arc>) -> Self { + Self { + handler_id, + filter_controller, + } + } +} + +impl TaskHandler for FilterTaskHandler { + fn handler_id(&self) -> &str { + &self.handler_id + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), anyhow::Error> { + let filter_controller = self.filter_controller.clone(); + Box::pin(async move { + if let TaskContent::Text(predicate) = content { + let _ = filter_controller + .write() + .await + .process(&predicate) + .await + .map_err(anyhow::Error::from); + } + Ok(()) + }) + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 70e791740b..5389ed6eea 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -1,45 +1,45 @@ -use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::GridCellIdParams; +use crate::dart_notification::{send_dart_notification, GridDartNotification}; +use crate::entities::CellPathParams; use crate::entities::*; -use crate::manager::{GridTaskSchedulerRwLock, GridUser}; +use crate::manager::GridUser; use crate::services::block_manager::GridBlockManager; - use crate::services::cell::{apply_cell_data_changeset, decode_any_cell_data, CellBytes}; use crate::services::field::{ default_type_option_builder_from_type, type_option_builder_from_bytes, type_option_builder_from_json_str, FieldBuilder, }; -use crate::services::filter::GridFilterService; -use crate::services::grid_view_manager::GridViewManager; + +use crate::services::filter::FilterType; +use crate::services::grid_editor_trait_impl::GridViewEditorDelegateImpl; use crate::services::persistence::block_index::BlockIndexCache; -use crate::services::row::{make_grid_blocks, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder}; +use crate::services::row::{GridBlock, RowRevisionBuilder}; +use crate::services::view_editor::{GridViewChanged, GridViewManager}; use bytes::Bytes; +use flowy_database::ConnectionPool; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::*; +use flowy_http_model::revision::Revision; use flowy_revision::{ - RevisionCloudService, RevisionCompress, RevisionManager, RevisionObjectDeserializer, RevisionObjectSerializer, + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, }; use flowy_sync::client_grid::{GridRevisionChangeset, GridRevisionPad, JsonDeserializer}; -use flowy_sync::entities::revision::Revision; use flowy_sync::errors::{CollaborateError, CollaborateResult}; use flowy_sync::util::make_operations_from_revisions; -use lib_infra::future::{wrap_future, FutureResult}; - +use flowy_task::TaskDispatcher; +use grid_rev_model::*; +use lib_infra::future::{to_future, FutureResult}; use lib_ot::core::EmptyAttributes; use std::collections::HashMap; use std::sync::Arc; -use tokio::sync::RwLock; +use tokio::sync::{broadcast, RwLock}; pub struct GridRevisionEditor { pub grid_id: String, + #[allow(dead_code)] user: Arc, grid_pad: Arc>, view_manager: Arc, - rev_manager: Arc, + rev_manager: Arc>>, block_manager: Arc, - - #[allow(dead_code)] - pub(crate) filter_service: Arc, } impl Drop for GridRevisionEditor { @@ -52,33 +52,28 @@ impl GridRevisionEditor { pub async fn new( grid_id: &str, user: Arc, - mut rev_manager: RevisionManager, + mut rev_manager: RevisionManager>, persistence: Arc, - task_scheduler: GridTaskSchedulerRwLock, + task_scheduler: Arc>, ) -> FlowyResult> { let token = user.token()?; let cloud = Arc::new(GridRevisionCloudService { token }); - let grid_pad = rev_manager.load::(Some(cloud)).await?; + let grid_pad = rev_manager.initialize::(Some(cloud)).await?; let rev_manager = Arc::new(rev_manager); let grid_pad = Arc::new(RwLock::new(grid_pad)); // Block manager let block_meta_revs = grid_pad.read().await.get_block_meta_revs(); let block_manager = Arc::new(GridBlockManager::new(&user, block_meta_revs, persistence).await?); - let filter_service = - GridFilterService::new(grid_pad.clone(), block_manager.clone(), task_scheduler.clone()).await; + let delegate = Arc::new(GridViewEditorDelegateImpl { + pad: grid_pad.clone(), + block_manager: block_manager.clone(), + task_scheduler, + }); // View manager - let view_manager = Arc::new( - GridViewManager::new( - grid_id.to_owned(), - user.clone(), - Arc::new(grid_pad.clone()), - Arc::new(block_manager.clone()), - Arc::new(task_scheduler.clone()), - ) - .await?, - ); + let view_manager = Arc::new(GridViewManager::new(grid_id.to_owned(), user.clone(), delegate).await?); + let editor = Arc::new(Self { grid_id: grid_id.to_owned(), user, @@ -86,13 +81,23 @@ impl GridRevisionEditor { rev_manager, block_manager, view_manager, - filter_service: Arc::new(filter_service), }); Ok(editor) } - /// Save the type-option data to disk and send a `GridNotification::DidUpdateField` notification + #[tracing::instrument(name = "close grid editor", level = "trace", skip_all)] + pub fn close(&self) { + let rev_manager = self.rev_manager.clone(); + let view_manager = self.view_manager.clone(); + let view_id = self.grid_id.clone(); + tokio::spawn(async move { + rev_manager.close().await; + view_manager.close(&view_id).await; + }); + } + + /// Save the type-option data to disk and send a `GridDartNotification::DidUpdateField` notification /// to dart side. /// /// It will do nothing if the passed-in type_option_data is empty @@ -109,10 +114,6 @@ impl GridRevisionEditor { field_id: &str, type_option_data: Vec, ) -> FlowyResult<()> { - if type_option_data.is_empty() { - return Ok(()); - } - let result = self.get_field_rev(field_id).await; if result.is_none() { tracing::warn!("Can't find the field with id: {}", field_id); @@ -205,7 +206,7 @@ impl GridRevisionEditor { pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> { let _ = self.modify(|grid_pad| Ok(grid_pad.delete_field_rev(field_id)?)).await?; let field_order = FieldIdPB::from(field_id); - let notified_changeset = FieldChangesetPB::delete(&self.grid_id, vec![field_order]); + let notified_changeset = GridFieldChangesetPB::delete(&self.grid_id, vec![field_order]); let _ = self.notify_did_update_grid(notified_changeset).await?; Ok(()) } @@ -307,10 +308,6 @@ impl GridRevisionEditor { #[tracing::instrument(level = "debug", skip_all, err)] async fn update_field_rev(&self, params: FieldChangesetParams, field_type: FieldType) -> FlowyResult<()> { let mut is_type_option_changed = false; - if !params.has_changes() { - return Ok(()); - } - let _ = self .modify(|grid| { let changeset = grid.modify_field(¶ms.field_id, |field| { @@ -334,11 +331,11 @@ impl GridRevisionEditor { } if let Some(type_option_data) = params.type_option_data { let deserializer = TypeOptionJsonDeserializer(field_type); + is_type_option_changed = true; match deserializer.deserialize(type_option_data) { Ok(json_str) => { let field_type = field.ty; field.insert_type_option_str(&field_type, json_str); - is_type_option_changed = true; } Err(err) => { tracing::error!("Deserialize data to type option json failed: {}", err); @@ -350,7 +347,6 @@ impl GridRevisionEditor { Ok(changeset) }) .await?; - let _ = self.view_manager.did_update_view_field(¶ms.field_id).await?; if is_type_option_changed { let _ = self .view_manager @@ -416,20 +412,16 @@ impl GridRevisionEditor { Ok(()) } - pub async fn get_rows(&self, block_id: &str) -> FlowyResult { - let block_ids = vec![block_id.to_owned()]; - let mut grid_block_snapshot = self.grid_block_snapshots(Some(block_ids)).await?; - - // For the moment, we only support one block. - // We can save the rows into multiple blocks and load them asynchronously in the future. - debug_assert_eq!(grid_block_snapshot.len(), 1); - if grid_block_snapshot.len() == 1 { - let snapshot = grid_block_snapshot.pop().unwrap(); - let rows = make_rows_from_row_revs(&snapshot.row_revs); - Ok(rows.into()) - } else { - Ok(vec![].into()) - } + pub async fn get_row_pbs(&self, block_id: &str) -> FlowyResult> { + let rows = self.block_manager.get_row_revs(block_id).await?; + let rows = self + .view_manager + .filter_rows(block_id, rows) + .await? + .into_iter() + .map(|row_rev| RowPB::from(&row_rev)) + .collect(); + Ok(rows) } pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { @@ -448,21 +440,25 @@ impl GridRevisionEditor { Ok(()) } + pub async fn subscribe_view_changed(&self) -> broadcast::Receiver { + self.view_manager.subscribe_view_changed().await + } + pub async fn duplicate_row(&self, _row_id: &str) -> FlowyResult<()> { Ok(()) } - pub async fn get_cell(&self, params: &GridCellIdParams) -> Option { + pub async fn get_cell(&self, params: &CellPathParams) -> Option { let (field_type, cell_bytes) = self.decode_any_cell_data(params).await?; - Some(GridCellPB::new(¶ms.field_id, field_type, cell_bytes.to_vec())) + Some(CellPB::new(¶ms.field_id, field_type, cell_bytes.to_vec())) } - pub async fn get_cell_bytes(&self, params: &GridCellIdParams) -> Option { + pub async fn get_cell_bytes(&self, params: &CellPathParams) -> Option { let (_, cell_data) = self.decode_any_cell_data(params).await?; Some(cell_data) } - async fn decode_any_cell_data(&self, params: &GridCellIdParams) -> Option<(FieldType, CellBytes)> { + async fn decode_any_cell_data(&self, params: &CellPathParams) -> Option<(FieldType, CellBytes)> { let field_rev = self.get_field_rev(¶ms.field_id).await?; let row_rev = self.block_manager.get_row_rev(¶ms.row_id).await.ok()??; let cell_rev = row_rev.cells.get(¶ms.field_id)?.clone(); @@ -481,7 +477,7 @@ impl GridRevisionEditor { } #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn update_cell(&self, cell_changeset: CellChangesetPB) -> FlowyResult<()> { + pub async fn update_cell_with_changeset(&self, cell_changeset: CellChangesetPB) -> FlowyResult<()> { let CellChangesetPB { grid_id, row_id, @@ -512,9 +508,21 @@ impl GridRevisionEditor { } } - pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult { - let block_snapshots = self.grid_block_snapshots(block_ids.clone()).await?; - make_grid_blocks(block_ids, block_snapshots) + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn update_cell( + &self, + grid_id: String, + row_id: String, + field_id: String, + content: T, + ) -> FlowyResult<()> { + self.update_cell_with_changeset(CellChangesetPB { + grid_id, + row_id, + field_id, + content: content.to_string(), + }) + .await } pub async fn get_block_meta_revs(&self) -> FlowyResult>> { @@ -522,65 +530,7 @@ impl GridRevisionEditor { Ok(block_meta_revs) } - pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { - let changesets = self.block_manager.delete_rows(row_orders).await?; - for changeset in changesets { - let _ = self.update_block(changeset).await?; - } - Ok(()) - } - - pub async fn get_grid_data(&self) -> FlowyResult { - let pad_read_guard = self.grid_pad.read().await; - let field_orders = pad_read_guard - .get_field_revs(None)? - .iter() - .map(FieldIdPB::from) - .collect(); - let mut block_orders = vec![]; - for block_rev in pad_read_guard.get_block_meta_revs() { - let row_orders = self.block_manager.get_row_orders(&block_rev.block_id).await?; - let block_order = BlockPB { - id: block_rev.block_id.clone(), - rows: row_orders, - }; - block_orders.push(block_order); - } - - Ok(GridPB { - id: self.grid_id.clone(), - fields: field_orders, - blocks: block_orders, - }) - } - - pub async fn get_grid_setting(&self) -> FlowyResult { - self.view_manager.get_setting().await - } - - pub async fn get_grid_filter(&self) -> FlowyResult> { - self.view_manager.get_filters().await - } - - pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - self.view_manager.insert_or_update_group(params).await - } - - pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.view_manager.delete_group(params).await - } - - pub async fn create_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { - let _ = self.view_manager.insert_or_update_filter(params).await?; - Ok(()) - } - - pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let _ = self.view_manager.delete_filter(params).await?; - Ok(()) - } - - pub async fn grid_block_snapshots(&self, block_ids: Option>) -> FlowyResult> { + pub async fn get_blocks(&self, block_ids: Option>) -> FlowyResult> { let block_ids = match block_ids { None => self .grid_pad @@ -592,8 +542,73 @@ impl GridRevisionEditor { .collect::>(), Some(block_ids) => block_ids, }; - let snapshots = self.block_manager.get_block_snapshots(Some(block_ids)).await?; - Ok(snapshots) + let blocks = self.block_manager.get_blocks(Some(block_ids)).await?; + Ok(blocks) + } + + pub async fn delete_rows(&self, row_orders: Vec) -> FlowyResult<()> { + let changesets = self.block_manager.delete_rows(row_orders).await?; + for changeset in changesets { + let _ = self.update_block(changeset).await?; + } + Ok(()) + } + + pub async fn get_grid(&self) -> FlowyResult { + let pad = self.grid_pad.read().await; + let fields = pad.get_field_revs(None)?.iter().map(FieldIdPB::from).collect(); + + let mut blocks = vec![]; + for block_rev in pad.get_block_meta_revs() { + let rows = self.get_row_pbs(&block_rev.block_id).await?; + let block = BlockPB { + id: block_rev.block_id.clone(), + rows, + }; + blocks.push(block); + } + + Ok(GridPB { + id: self.grid_id.clone(), + fields, + blocks, + }) + } + + pub async fn get_setting(&self) -> FlowyResult { + self.view_manager.get_setting().await + } + + pub async fn get_all_filters(&self) -> FlowyResult> { + Ok(self + .view_manager + .get_all_filters() + .await? + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect()) + } + + pub async fn get_filters(&self, filter_id: FilterType) -> FlowyResult>> { + self.view_manager.get_filters(&filter_id).await + } + + pub async fn insert_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + self.view_manager.insert_or_update_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.view_manager.delete_group(params).await + } + + pub async fn create_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { + let _ = self.view_manager.insert_or_update_filter(params).await?; + Ok(()) + } + + pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let _ = self.view_manager.delete_filter(params).await?; + Ok(()) } pub async fn move_row(&self, params: MoveRowParams) -> FlowyResult<()> { @@ -639,7 +654,7 @@ impl GridRevisionEditor { let block_manager = self.block_manager.clone(); self.view_manager .move_group_row(row_rev, to_group_id, to_row_id.clone(), |row_changeset| { - wrap_future(async move { + to_future(async move { tracing::trace!("Row data changed: {:?}", row_changeset); let cell_changesets = row_changeset .cell_by_field_id @@ -680,7 +695,7 @@ impl GridRevisionEditor { if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(&field_id) { let delete_field_order = FieldIdPB::from(field_id); let insert_field = IndexFieldPB::from_field_rev(field_rev, index); - let notified_changeset = FieldChangesetPB { + let notified_changeset = GridFieldChangesetPB { grid_id: self.grid_id.clone(), inserted_fields: vec![insert_field], deleted_fields: vec![delete_field_order], @@ -764,17 +779,9 @@ impl GridRevisionEditor { async fn apply_change(&self, change: GridRevisionChangeset) -> FlowyResult<()> { let GridRevisionChangeset { operations: delta, md5 } = change; - let user_id = self.user.user_id()?; let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair(); let delta_data = delta.json_bytes(); - let revision = Revision::new( - &self.rev_manager.object_id, - base_rev_id, - rev_id, - delta_data, - &user_id, - md5, - ); + let revision = Revision::new(&self.rev_manager.object_id, base_rev_id, rev_id, delta_data, md5); let _ = self.rev_manager.add_local_revision(&revision).await?; Ok(()) } @@ -790,7 +797,7 @@ impl GridRevisionEditor { async fn notify_did_insert_grid_field(&self, field_id: &str) -> FlowyResult<()> { if let Some((index, field_rev)) = self.grid_pad.read().await.get_field_rev(field_id) { let index_field = IndexFieldPB::from_field_rev(field_rev, index); - let notified_changeset = FieldChangesetPB::insert(&self.grid_id, vec![index_field]); + let notified_changeset = GridFieldChangesetPB::insert(&self.grid_id, vec![index_field]); let _ = self.notify_did_update_grid(notified_changeset).await?; } Ok(()) @@ -806,10 +813,10 @@ impl GridRevisionEditor { .map(|(index, field)| (index, field.clone())) { let updated_field = FieldPB::from(field_rev); - let notified_changeset = FieldChangesetPB::update(&self.grid_id, vec![updated_field.clone()]); + let notified_changeset = GridFieldChangesetPB::update(&self.grid_id, vec![updated_field.clone()]); let _ = self.notify_did_update_grid(notified_changeset).await?; - send_dart_notification(field_id, GridNotification::DidUpdateField) + send_dart_notification(field_id, GridDartNotification::DidUpdateField) .payload(updated_field) .send(); } @@ -817,8 +824,8 @@ impl GridRevisionEditor { Ok(()) } - async fn notify_did_update_grid(&self, changeset: FieldChangesetPB) -> FlowyResult<()> { - send_dart_notification(&self.grid_id, GridNotification::DidUpdateGridField) + async fn notify_did_update_grid(&self, changeset: GridFieldChangesetPB) -> FlowyResult<()> { + send_dart_notification(&self.grid_id, GridDartNotification::DidUpdateGridField) .payload(changeset) .send(); Ok(()) @@ -827,7 +834,7 @@ impl GridRevisionEditor { #[cfg(feature = "flowy_unit_test")] impl GridRevisionEditor { - pub fn rev_manager(&self) -> Arc { + pub fn rev_manager(&self) -> Arc>> { self.rev_manager.clone() } } @@ -842,7 +849,7 @@ impl RevisionObjectDeserializer for GridRevisionSerde { } } impl RevisionObjectSerializer for GridRevisionSerde { - fn serialize_revisions(revisions: Vec) -> FlowyResult { + fn combine_revisions(revisions: Vec) -> FlowyResult { let operations = make_operations_from_revisions::(revisions)?; Ok(operations.json_bytes()) } @@ -859,11 +866,11 @@ impl RevisionCloudService for GridRevisionCloudService { } } -pub struct GridRevisionCompactor(); +pub struct GridRevisionCompress(); -impl RevisionCompress for GridRevisionCompactor { - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult { - GridRevisionSerde::serialize_revisions(revisions) +impl RevisionMergeable for GridRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + GridRevisionSerde::combine_revisions(revisions) } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs deleted file mode 100644 index f5c45811dd..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor_task.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::manager::GridTaskSchedulerRwLock; -use crate::services::grid_editor::GridRevisionEditor; -use crate::services::tasks::{GridTaskHandler, Task, TaskContent, TaskId}; -use flowy_error::FlowyError; -use futures::future::BoxFuture; -use lib_infra::future::BoxResultFuture; - -pub(crate) trait GridServiceTaskScheduler: Send + Sync + 'static { - fn gen_task_id(&self) -> BoxFuture; - fn add_task(&self, task: Task) -> BoxFuture<()>; -} - -impl GridTaskHandler for GridRevisionEditor { - fn handler_id(&self) -> &str { - &self.grid_id - } - - fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError> { - Box::pin(async move { - match content { - TaskContent::Snapshot => {} - TaskContent::Group => {} - TaskContent::Filter(context) => self.filter_service.process(context).await?, - } - Ok(()) - }) - } -} - -impl GridServiceTaskScheduler for GridTaskSchedulerRwLock { - fn gen_task_id(&self) -> BoxFuture { - let this = self.clone(); - Box::pin(async move { this.read().await.next_task_id() }) - } - - fn add_task(&self, task: Task) -> BoxFuture<()> { - let this = self.clone(); - Box::pin(async move { - this.write().await.add_task(task); - }) - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs index 43bc74bf12..fd257dbb7d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor_trait_impl.rs @@ -1,15 +1,24 @@ -use crate::services::grid_view_manager::GridViewFieldDelegate; -use flowy_grid_data_model::revision::FieldRevision; +use crate::services::block_manager::GridBlockManager; +use crate::services::row::GridBlock; +use crate::services::view_editor::GridViewEditorDelegate; use flowy_sync::client_grid::GridRevisionPad; -use lib_infra::future::{wrap_future, AFFuture}; +use flowy_task::TaskDispatcher; +use grid_rev_model::{FieldRevision, RowRevision}; +use lib_infra::future::{to_future, Fut}; use std::sync::Arc; use tokio::sync::RwLock; -impl GridViewFieldDelegate for Arc> { - fn get_field_revs(&self) -> AFFuture>> { - let pad = self.clone(); - wrap_future(async move { - match pad.read().await.get_field_revs(None) { +pub(crate) struct GridViewEditorDelegateImpl { + pub(crate) pad: Arc>, + pub(crate) block_manager: Arc, + pub(crate) task_scheduler: Arc>, +} + +impl GridViewEditorDelegate for GridViewEditorDelegateImpl { + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + let pad = self.pad.clone(); + to_future(async move { + match pad.read().await.get_field_revs(field_ids) { Ok(field_revs) => field_revs, Err(e) => { tracing::error!("[GridViewRevisionDelegate] get field revisions failed: {}", e); @@ -19,14 +28,47 @@ impl GridViewFieldDelegate for Arc> { }) } - fn get_field_rev(&self, field_id: &str) -> AFFuture>> { - let pad = self.clone(); + fn get_field_rev(&self, field_id: &str) -> Fut>> { + let pad = self.pad.clone(); let field_id = field_id.to_owned(); - wrap_future(async move { - pad.read() - .await - .get_field_rev(&field_id) - .map(|(_, field_rev)| field_rev.clone()) + to_future(async move { Some(pad.read().await.get_field_rev(&field_id)?.1.clone()) }) + } + + fn index_of_row(&self, row_id: &str) -> Fut> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_future(async move { block_manager.index_of_row(&row_id).await }) + } + + fn get_row_rev(&self, row_id: &str) -> Fut>> { + let block_manager = self.block_manager.clone(); + let row_id = row_id.to_owned(); + to_future(async move { + match block_manager.get_row_rev(&row_id).await { + Ok(row_rev) => row_rev, + Err(_) => None, + } }) } + + fn get_row_revs(&self) -> Fut>> { + let block_manager = self.block_manager.clone(); + + to_future(async move { + let blocks = block_manager.get_blocks(None).await.unwrap(); + blocks + .into_iter() + .flat_map(|block| block.row_revs) + .collect::>>() + }) + } + + fn get_blocks(&self) -> Fut> { + let block_manager = self.block_manager.clone(); + to_future(async move { block_manager.get_blocks(None).await.unwrap_or_default() }) + } + + fn get_task_scheduler(&self) -> Arc> { + self.task_scheduler.clone() + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs deleted file mode 100644 index 718c9ee871..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs +++ /dev/null @@ -1,602 +0,0 @@ -use crate::dart_notification::{send_dart_notification, GridNotification}; -use crate::entities::{ - CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridGroupConfigurationPB, - GridLayout, GridLayoutPB, GridSettingPB, GroupChangesetPB, GroupPB, GroupViewChangesetPB, InsertFilterParams, - InsertGroupParams, InsertedGroupPB, InsertedRowPB, MoveGroupParams, RepeatedGridFilterConfigurationPB, - RepeatedGridGroupConfigurationPB, RowPB, -}; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::grid_view_manager::{GridViewFieldDelegate, GridViewRowDelegate}; -use crate::services::group::{ - default_group_configuration, find_group_field, make_group_controller, GroupConfigurationReader, - GroupConfigurationWriter, GroupController, MoveGroupRowContext, -}; -use bytes::Bytes; -use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ - gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision, - RowChangeset, RowRevision, -}; -use flowy_revision::{ - RevisionCloudService, RevisionCompress, RevisionManager, RevisionObjectDeserializer, RevisionObjectSerializer, -}; -use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; -use flowy_sync::entities::revision::Revision; -use flowy_sync::util::make_operations_from_revisions; -use lib_infra::future::{wrap_future, AFFuture, FutureResult}; -use lib_ot::core::EmptyAttributes; -use std::future::Future; -use std::sync::Arc; -use tokio::sync::RwLock; - -#[allow(dead_code)] -pub struct GridViewRevisionEditor { - user_id: String, - view_id: String, - pad: Arc>, - rev_manager: Arc, - field_delegate: Arc, - row_delegate: Arc, - group_controller: Arc>>, - scheduler: Arc, -} -impl GridViewRevisionEditor { - #[tracing::instrument(level = "trace", skip_all, err)] - pub(crate) async fn new( - user_id: &str, - token: &str, - view_id: String, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, - mut rev_manager: RevisionManager, - ) -> FlowyResult { - let cloud = Arc::new(GridViewRevisionCloudService { - token: token.to_owned(), - }); - let view_revision_pad = rev_manager.load::(Some(cloud)).await?; - let pad = Arc::new(RwLock::new(view_revision_pad)); - let rev_manager = Arc::new(rev_manager); - let group_controller = new_group_controller( - user_id.to_owned(), - view_id.clone(), - pad.clone(), - rev_manager.clone(), - field_delegate.clone(), - row_delegate.clone(), - ) - .await?; - let user_id = user_id.to_owned(); - Ok(Self { - pad, - user_id, - view_id, - rev_manager, - scheduler, - field_delegate, - row_delegate, - group_controller: Arc::new(RwLock::new(group_controller)), - }) - } - - pub(crate) async fn duplicate_view_data(&self) -> FlowyResult { - let json_str = self.pad.read().await.json_str()?; - Ok(json_str) - } - - pub(crate) async fn will_create_view_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - if params.group_id.is_none() { - return; - } - let group_id = params.group_id.as_ref().unwrap(); - let _ = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.will_create_row(row_rev, &field_rev, group_id); - Ok(()) - }) - .await; - } - - pub(crate) async fn did_create_view_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - // Send the group notification if the current view has groups - match params.group_id.as_ref() { - None => {} - Some(group_id) => { - let index = match params.start_row_id { - None => Some(0), - Some(_) => None, - }; - - self.group_controller.write().await.did_create_row(row_pb, group_id); - let inserted_row = InsertedRowPB { - row: row_pb.clone(), - index, - is_new: true, - }; - let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]); - self.notify_did_update_group(changeset).await; - } - } - } - - #[tracing::instrument(level = "trace", skip_all)] - pub(crate) async fn did_delete_view_row(&self, row_rev: &RowRevision) { - // Send the group notification if the current view has groups; - let changesets = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.did_delete_delete_row(row_rev, &field_rev) - }) - .await; - - tracing::trace!("Delete row in view changeset: {:?}", changesets); - if let Some(changesets) = changesets { - for changeset in changesets { - self.notify_did_update_group(changeset).await; - } - } - } - - pub(crate) async fn did_update_view_cell(&self, row_rev: &RowRevision) { - let changesets = self - .mut_group_controller(|group_controller, field_rev| { - group_controller.did_update_group_row(row_rev, &field_rev) - }) - .await; - - if let Some(changesets) = changesets { - for changeset in changesets { - self.notify_did_update_group(changeset).await; - } - } - } - - pub(crate) async fn move_view_group_row( - &self, - row_rev: &RowRevision, - row_changeset: &mut RowChangeset, - to_group_id: &str, - to_row_id: Option, - ) -> Vec { - let changesets = self - .mut_group_controller(|group_controller, field_rev| { - let move_row_context = MoveGroupRowContext { - row_rev, - row_changeset, - field_rev: field_rev.as_ref(), - to_group_id, - to_row_id, - }; - - let changesets = group_controller.move_group_row(move_row_context)?; - Ok(changesets) - }) - .await; - - changesets.unwrap_or_default() - } - /// Only call once after grid view editor initialized - #[tracing::instrument(level = "trace", skip(self))] - pub(crate) async fn load_view_groups(&self) -> FlowyResult> { - let groups = self.group_controller.read().await.groups(); - tracing::trace!("Number of groups: {}", groups.len()); - Ok(groups.into_iter().map(GroupPB::from).collect()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn move_view_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - let _ = self - .group_controller - .write() - .await - .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; - match self.group_controller.read().await.get_group(¶ms.from_group_id) { - None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), - Some((index, group)) => { - let inserted_group = InsertedGroupPB { - group: GroupPB::from(group), - index: index as i32, - }; - - let changeset = GroupViewChangesetPB { - view_id: self.view_id.clone(), - inserted_groups: vec![inserted_group], - deleted_groups: vec![params.from_group_id.clone()], - update_groups: vec![], - new_groups: vec![], - }; - - self.notify_did_update_view(changeset).await; - } - } - Ok(()) - } - - pub(crate) async fn group_id(&self) -> String { - self.group_controller.read().await.field_id().to_owned() - } - - pub(crate) async fn get_view_setting(&self) -> GridSettingPB { - let field_revs = self.field_delegate.get_field_revs().await; - let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs); - grid_setting - } - - pub(crate) async fn get_view_filters(&self) -> Vec { - let field_revs = self.field_delegate.get_field_revs().await; - match self.pad.read().await.get_all_filters(&field_revs) { - None => vec![], - Some(filters) => filters - .into_values() - .flatten() - .map(|filter| GridFilterConfigurationPB::from(filter.as_ref())) - .collect(), - } - } - - /// Initialize new group when grouping by a new field - /// - pub(crate) async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - if let Some(field_rev) = self.field_delegate.get_field_rev(¶ms.field_id).await { - let _ = self - .modify(|pad| { - let configuration = default_group_configuration(&field_rev); - let changeset = pad.insert_or_update_group_configuration( - ¶ms.field_id, - ¶ms.field_type_rev, - configuration, - )?; - Ok(changeset) - }) - .await?; - } - if self.group_controller.read().await.field_id() != params.field_id { - let _ = self.group_by_view_field(¶ms.field_id).await?; - self.notify_did_update_setting().await; - } - Ok(()) - } - - pub(crate) async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - self.modify(|pad| { - let changeset = pad.delete_filter(¶ms.field_id, ¶ms.field_type_rev, ¶ms.group_id)?; - Ok(changeset) - }) - .await - } - - pub(crate) async fn insert_view_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { - self.modify(|pad| { - let filter_rev = FilterConfigurationRevision { - id: gen_grid_filter_id(), - field_id: params.field_id.clone(), - condition: params.condition, - content: params.content, - }; - let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?; - Ok(changeset) - }) - .await - } - - pub(crate) async fn delete_view_filter(&self, delete_filter: DeleteFilterParams) -> FlowyResult<()> { - self.modify(|pad| { - let changeset = pad.delete_filter( - &delete_filter.field_id, - &delete_filter.field_type_rev, - &delete_filter.filter_id, - )?; - Ok(changeset) - }) - .await - } - #[tracing::instrument(level = "trace", skip_all, err)] - pub(crate) async fn did_update_view_field(&self, _field_id: &str) -> FlowyResult<()> { - // Do nothing - Ok(()) - } - - /// - /// - /// # Arguments - /// - /// * `field_id`: - /// - #[tracing::instrument(level = "debug", skip_all, err)] - pub(crate) async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> { - if let Some(field_rev) = self.field_delegate.get_field_rev(field_id).await { - let new_group_controller = new_group_controller_with_field_rev( - self.user_id.clone(), - self.view_id.clone(), - self.pad.clone(), - self.rev_manager.clone(), - field_rev, - self.row_delegate.clone(), - ) - .await?; - - let new_groups = new_group_controller.groups().into_iter().map(GroupPB::from).collect(); - - *self.group_controller.write().await = new_group_controller; - let changeset = GroupViewChangesetPB { - view_id: self.view_id.clone(), - new_groups, - ..Default::default() - }; - - debug_assert!(!changeset.is_empty()); - if !changeset.is_empty() { - send_dart_notification(&changeset.view_id, GridNotification::DidGroupByNewField) - .payload(changeset) - .send(); - } - } - Ok(()) - } - - async fn notify_did_update_setting(&self) { - let setting = self.get_view_setting().await; - send_dart_notification(&self.view_id, GridNotification::DidUpdateGridSetting) - .payload(setting) - .send(); - } - - pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) { - send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup) - .payload(changeset) - .send(); - } - - async fn notify_did_update_view(&self, changeset: GroupViewChangesetPB) { - send_dart_notification(&self.view_id, GridNotification::DidUpdateGroupView) - .payload(changeset) - .send(); - } - - async fn modify(&self, f: F) -> FlowyResult<()> - where - F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult>, - { - let mut write_guard = self.pad.write().await; - match f(&mut *write_guard)? { - None => {} - Some(change) => { - let _ = apply_change(&self.user_id, self.rev_manager.clone(), change).await?; - } - } - Ok(()) - } - - async fn mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(&mut Box, Arc) -> FlowyResult, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.field_delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let mut write_guard = self.group_controller.write().await; - f(&mut write_guard, field_rev).ok() - } - } - } - - #[allow(dead_code)] - async fn async_mut_group_controller(&self, f: F) -> Option - where - F: FnOnce(Arc>>, Arc) -> O, - O: Future> + Sync + 'static, - { - let group_field_id = self.group_controller.read().await.field_id().to_owned(); - match self.field_delegate.get_field_rev(&group_field_id).await { - None => None, - Some(field_rev) => { - let _write_guard = self.group_controller.write().await; - f(self.group_controller.clone(), field_rev).await.ok() - } - } - } -} - -async fn new_group_controller( - user_id: String, - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc, - field_delegate: Arc, - row_delegate: Arc, -) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); - let field_revs = field_delegate.get_field_revs().await; - let layout = view_rev_pad.read().await.layout(); - // Read the group field or find a new group field - let field_rev = configuration_reader - .get_configuration() - .await - .and_then(|configuration| { - field_revs - .iter() - .find(|field_rev| field_rev.id == configuration.field_id) - .cloned() - }) - .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); - - new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_delegate).await -} - -/// Returns a [GroupController] -/// -/// # Arguments -/// -/// * `user_id`: -/// * `view_id`: -/// * `view_rev_pad`: -/// * `rev_manager`: -/// * `field_rev`: -/// * `row_delegate`: -/// -async fn new_group_controller_with_field_rev( - user_id: String, - view_id: String, - view_rev_pad: Arc>, - rev_manager: Arc, - field_rev: Arc, - row_delegate: Arc, -) -> FlowyResult> { - let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); - let configuration_writer = GroupConfigurationWriterImpl { - user_id, - rev_manager, - view_pad: view_rev_pad, - }; - let row_revs = row_delegate.gv_row_revs().await; - make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await -} - -async fn apply_change( - user_id: &str, - rev_manager: Arc, - change: GridViewRevisionChangeset, -) -> FlowyResult<()> { - let GridViewRevisionChangeset { operations: delta, md5 } = change; - let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); - let delta_data = delta.json_bytes(); - let revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, delta_data, user_id, md5); - let _ = rev_manager.add_local_revision(&revision).await?; - Ok(()) -} - -struct GridViewRevisionCloudService { - #[allow(dead_code)] - token: String, -} - -impl RevisionCloudService for GridViewRevisionCloudService { - #[tracing::instrument(level = "trace", skip(self))] - fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { - FutureResult::new(async move { Ok(vec![]) }) - } -} - -pub struct GridViewRevisionSerde(); -impl RevisionObjectDeserializer for GridViewRevisionSerde { - type Output = GridViewRevisionPad; - - fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { - let pad = GridViewRevisionPad::from_revisions(object_id, revisions)?; - Ok(pad) - } -} - -impl RevisionObjectSerializer for GridViewRevisionSerde { - fn serialize_revisions(revisions: Vec) -> FlowyResult { - let operations = make_operations_from_revisions::(revisions)?; - Ok(operations.json_bytes()) - } -} - -pub struct GridViewRevisionCompactor(); -impl RevisionCompress for GridViewRevisionCompactor { - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult { - GridViewRevisionSerde::serialize_revisions(revisions) - } -} - -struct GroupConfigurationReaderImpl(Arc>); - -impl GroupConfigurationReader for GroupConfigurationReaderImpl { - fn get_configuration(&self) -> AFFuture>> { - let view_pad = self.0.clone(); - wrap_future(async move { - let mut groups = view_pad.read().await.get_all_groups(); - if groups.is_empty() { - None - } else { - debug_assert_eq!(groups.len(), 1); - Some(groups.pop().unwrap()) - } - }) - } -} - -struct GroupConfigurationWriterImpl { - user_id: String, - rev_manager: Arc, - view_pad: Arc>, -} - -impl GroupConfigurationWriter for GroupConfigurationWriterImpl { - fn save_configuration( - &self, - field_id: &str, - field_type: FieldTypeRevision, - group_configuration: GroupConfigurationRevision, - ) -> AFFuture> { - let user_id = self.user_id.clone(); - let rev_manager = self.rev_manager.clone(); - let view_pad = self.view_pad.clone(); - let field_id = field_id.to_owned(); - - wrap_future(async move { - let changeset = view_pad.write().await.insert_or_update_group_configuration( - &field_id, - &field_type, - group_configuration, - )?; - - if let Some(changeset) = changeset { - let _ = apply_change(&user_id, rev_manager, changeset).await?; - } - Ok(()) - }) - } -} - -pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc]) -> GridSettingPB { - let layout_type: GridLayout = view_pad.layout.clone().into(); - let filter_configurations = view_pad - .get_all_filters(field_revs) - .map(|filters_by_field_id| { - filters_by_field_id - .into_iter() - .flat_map(|(_, v)| { - let repeated_filter: RepeatedGridFilterConfigurationPB = v.into(); - repeated_filter.items - }) - .collect::>() - }) - .unwrap_or_default(); - - let group_configurations = view_pad - .get_groups_by_field_revs(field_revs) - .map(|groups_by_field_id| { - groups_by_field_id - .into_iter() - .flat_map(|(_, v)| { - let repeated_group: RepeatedGridGroupConfigurationPB = v.into(); - repeated_group.items - }) - .collect::>() - }) - .unwrap_or_default(); - - GridSettingPB { - layouts: GridLayoutPB::all(), - layout_type, - filter_configurations: filter_configurations.into(), - group_configurations: group_configurations.into(), - } -} - -#[cfg(test)] -mod tests { - use lib_ot::core::Delta; - - #[test] - fn test() { - let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#; - let _delta_1 = Delta::from_json(s1).unwrap(); - - let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#; - let _delta_2 = Delta::from_json(s2).unwrap(); - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs deleted file mode 100644 index 027f7cfc80..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::entities::{ - CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridFilterConfigurationPB, GridSettingPB, - InsertFilterParams, InsertGroupParams, MoveGroupParams, RepeatedGridGroupPB, RowPB, -}; -use crate::manager::GridUser; -use crate::services::grid_editor_task::GridServiceTaskScheduler; -use crate::services::grid_view_editor::{GridViewRevisionCompactor, GridViewRevisionEditor}; - -use dashmap::DashMap; -use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{FieldRevision, RowChangeset, RowRevision}; -use flowy_revision::disk::SQLiteGridViewRevisionPersistence; -use flowy_revision::{RevisionManager, RevisionPersistence, SQLiteRevisionSnapshotPersistence}; -use lib_infra::future::AFFuture; -use std::sync::Arc; - -type ViewId = String; - -pub trait GridViewFieldDelegate: Send + Sync + 'static { - fn get_field_revs(&self) -> AFFuture>>; - fn get_field_rev(&self, field_id: &str) -> AFFuture>>; -} - -pub trait GridViewRowDelegate: Send + Sync + 'static { - fn gv_index_of_row(&self, row_id: &str) -> AFFuture>; - fn gv_get_row_rev(&self, row_id: &str) -> AFFuture>>; - fn gv_row_revs(&self) -> AFFuture>>; -} - -pub(crate) struct GridViewManager { - grid_id: String, - user: Arc, - field_delegate: Arc, - row_delegate: Arc, - view_editors: DashMap>, - scheduler: Arc, -} - -impl GridViewManager { - pub(crate) async fn new( - grid_id: String, - user: Arc, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, - ) -> FlowyResult { - Ok(Self { - grid_id, - user, - scheduler, - field_delegate, - row_delegate, - view_editors: DashMap::default(), - }) - } - - pub(crate) async fn duplicate_grid_view(&self) -> FlowyResult { - let editor = self.get_default_view_editor().await?; - let view_data = editor.duplicate_view_data().await?; - Ok(view_data) - } - - /// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams]. - pub(crate) async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { - for view_editor in self.view_editors.iter() { - view_editor.will_create_view_row(row_rev, params).await; - } - } - - /// Notify the view that the row was created. For the moment, the view is just sending notifications. - pub(crate) async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { - for view_editor in self.view_editors.iter() { - view_editor.did_create_view_row(row_pb, params).await; - } - } - - /// Insert/Delete the group's row if the corresponding cell data was changed. - pub(crate) async fn did_update_cell(&self, row_id: &str) { - match self.row_delegate.gv_get_row_rev(row_id).await { - None => { - tracing::warn!("Can not find the row in grid view"); - } - Some(row_rev) => { - for view_editor in self.view_editors.iter() { - view_editor.did_update_view_cell(&row_rev).await; - } - } - } - } - - pub(crate) async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - let _ = view_editor.group_by_view_field(field_id).await?; - Ok(()) - } - - pub(crate) async fn did_delete_row(&self, row_rev: Arc) { - for view_editor in self.view_editors.iter() { - view_editor.did_delete_view_row(&row_rev).await; - } - } - - pub(crate) async fn get_setting(&self) -> FlowyResult { - let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_view_setting().await) - } - - pub(crate) async fn get_filters(&self) -> FlowyResult> { - let view_editor = self.get_default_view_editor().await?; - Ok(view_editor.get_view_filters().await) - } - - pub(crate) async fn insert_or_update_filter(&self, params: InsertFilterParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.insert_view_filter(params).await - } - - pub(crate) async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.delete_view_filter(params).await - } - - pub(crate) async fn load_groups(&self) -> FlowyResult { - let view_editor = self.get_default_view_editor().await?; - let groups = view_editor.load_view_groups().await?; - Ok(RepeatedGridGroupPB { items: groups }) - } - - pub(crate) async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.initialize_new_group(params).await - } - - pub(crate) async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - view_editor.delete_view_group(params).await - } - - pub(crate) async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - let _ = view_editor.move_view_group(params).await?; - Ok(()) - } - - /// It may generate a RowChangeset when the Row was moved from one group to another. - /// The return value, [RowChangeset], contains the changes made by the groups. - /// - pub(crate) async fn move_group_row( - &self, - row_rev: Arc, - to_group_id: String, - to_row_id: Option, - recv_row_changeset: impl FnOnce(RowChangeset) -> AFFuture<()>, - ) -> FlowyResult<()> { - let mut row_changeset = RowChangeset::new(row_rev.id.clone()); - let view_editor = self.get_default_view_editor().await?; - let group_changesets = view_editor - .move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone()) - .await; - - if !row_changeset.is_empty() { - recv_row_changeset(row_changeset).await; - } - - for group_changeset in group_changesets { - view_editor.notify_did_update_group(group_changeset).await; - } - - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn did_update_view_field(&self, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - // Update the group if the group_id equal to the field_id - if view_editor.group_id().await != field_id { - return Ok(()); - } - let _ = view_editor.did_update_view_field(field_id).await?; - Ok(()) - } - - /// Notifies the view's field type-option data is changed - /// For the moment, only the groups will be generated after the type-option data changed. A - /// [FieldRevision] has a property named type_options contains a list of type-option data. - /// # Arguments - /// - /// * `field_id`: the id of the field in current view - /// - #[tracing::instrument(level = "trace", skip(self), err)] - pub(crate) async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> { - let view_editor = self.get_default_view_editor().await?; - let _ = view_editor.group_by_view_field(field_id).await?; - Ok(()) - } - - pub(crate) async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { - debug_assert!(!view_id.is_empty()); - match self.view_editors.get(view_id) { - None => { - let editor = Arc::new( - make_view_editor( - &self.user, - view_id, - self.field_delegate.clone(), - self.row_delegate.clone(), - self.scheduler.clone(), - ) - .await?, - ); - self.view_editors.insert(view_id.to_owned(), editor.clone()); - Ok(editor) - } - Some(view_editor) => Ok(view_editor.clone()), - } - } - - async fn get_default_view_editor(&self) -> FlowyResult> { - self.get_view_editor(&self.grid_id).await - } -} - -async fn make_view_editor( - user: &Arc, - view_id: &str, - field_delegate: Arc, - row_delegate: Arc, - scheduler: Arc, -) -> FlowyResult { - let rev_manager = make_grid_view_rev_manager(user, view_id).await?; - let user_id = user.user_id()?; - let token = user.token()?; - let view_id = view_id.to_owned(); - - GridViewRevisionEditor::new( - &user_id, - &token, - view_id, - field_delegate, - row_delegate, - scheduler, - rev_manager, - ) - .await -} - -pub async fn make_grid_view_rev_manager(user: &Arc, view_id: &str) -> FlowyResult { - let user_id = user.user_id()?; - let pool = user.db_pool()?; - - let disk_cache = SQLiteGridViewRevisionPersistence::new(&user_id, pool.clone()); - let rev_persistence = RevisionPersistence::new(&user_id, view_id, disk_cache); - let rev_compactor = GridViewRevisionCompactor(); - - let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(view_id, pool); - Ok(RevisionManager::new( - &user_id, - view_id, - rev_persistence, - rev_compactor, - snapshot_persistence, - )) -} diff --git a/frontend/rust-lib/flowy-grid/src/services/group/action.rs b/frontend/rust-lib/flowy-grid/src/services/group/action.rs index 27f0a3d90c..17d0f9cc06 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/action.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/action.rs @@ -1,9 +1,9 @@ -use crate::entities::{GroupChangesetPB, GroupViewChangesetPB}; +use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB}; use crate::services::cell::CellDataIsEmpty; use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::Group; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{CellRevision, FieldRevision, RowRevision}; +use grid_rev_model::{CellRevision, FieldRevision, RowRevision}; use std::sync::Arc; /// Using polymorphism to provides the customs action for different group controller. @@ -31,13 +31,17 @@ pub trait GroupControllerCustomActions: Send + Sync { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, - ) -> Vec; + ) -> Vec; /// Deletes the row from the group - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; + fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec; /// Move row from one group to another - fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec; + fn move_row( + &mut self, + cell_data: &Self::CellDataType, + context: MoveGroupRowContext, + ) -> Vec; } /// Defines the shared actions any group controller can perform. @@ -46,7 +50,7 @@ pub trait GroupControllerSharedActions: Send + Sync { fn field_id(&self) -> &str; /// Returns number of groups the current field has - fn groups(&self) -> Vec; + fn groups(&self) -> Vec<&Group>; /// Returns the index and the group data with group_id fn get_group(&self, group_id: &str) -> Option<(usize, Group)>; @@ -62,17 +66,17 @@ pub trait GroupControllerSharedActions: Send + Sync { &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult>; + ) -> FlowyResult>; /// Remove the row from the group if the row gets deleted fn did_delete_delete_row( &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult>; + ) -> FlowyResult>; /// Move the row from one group to another group - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult>; + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult>; /// Update the group if the corresponding field is changed fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult>; diff --git a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs index ab554f195e..01f30dbf6e 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/configuration.rs @@ -1,18 +1,18 @@ use crate::entities::{GroupPB, GroupViewChangesetPB}; use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group}; use flowy_error::{FlowyError, FlowyResult}; -use flowy_grid_data_model::revision::{ +use grid_rev_model::{ FieldRevision, FieldTypeRevision, GroupConfigurationContentSerde, GroupConfigurationRevision, GroupRevision, }; use indexmap::IndexMap; -use lib_infra::future::AFFuture; +use lib_infra::future::Fut; use std::collections::HashMap; use std::fmt::Formatter; use std::marker::PhantomData; use std::sync::Arc; pub trait GroupConfigurationReader: Send + Sync + 'static { - fn get_configuration(&self) -> AFFuture>>; + fn get_configuration(&self) -> Fut>>; } pub trait GroupConfigurationWriter: Send + Sync + 'static { @@ -21,7 +21,7 @@ pub trait GroupConfigurationWriter: Send + Sync + 'static { field_id: &str, field_type: FieldTypeRevision, group_configuration: GroupConfigurationRevision, - ) -> AFFuture>; + ) -> Fut>; } impl std::fmt::Display for GroupContext { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs index 899193b5d4..00624c0651 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller.rs @@ -1,10 +1,10 @@ -use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB}; +use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB}; use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty}; use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions}; use crate::services::group::configuration::GroupContext; use crate::services::group::entities::Group; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{ +use grid_rev_model::{ FieldRevision, GroupConfigurationContentSerde, GroupRevision, RowChangeset, RowRevision, TypeOptionDataDeserializer, }; use std::marker::PhantomData; @@ -89,8 +89,8 @@ where fn update_default_group( &mut self, row_rev: &RowRevision, - other_group_changesets: &[GroupChangesetPB], - ) -> Option { + other_group_changesets: &[GroupRowsNotificationPB], + ) -> Option { let default_group = self.group_ctx.get_mut_no_status_group()?; // [other_group_inserted_row] contains all the inserted rows except the default group. @@ -113,7 +113,7 @@ where }) .collect::>(); - let mut changeset = GroupChangesetPB::new(default_group.id.clone()); + let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone()); if !default_group_inserted_row.is_empty() { changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into())); default_group.add_row(row_rev.into()); @@ -165,8 +165,8 @@ where &self.field_id } - fn groups(&self) -> Vec { - self.group_ctx.groups().into_iter().cloned().collect() + fn groups(&self) -> Vec<&Group> { + self.group_ctx.groups() } fn get_group(&self, group_id: &str) -> Option<(usize, Group)> { @@ -222,7 +222,7 @@ where &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_data = cell_bytes.parser::

()?; @@ -244,7 +244,7 @@ where &mut self, row_rev: &RowRevision, field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { // if the cell_rev is none, then the row must in the default group. if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; @@ -264,7 +264,7 @@ where if !no_status_group.contains_row(&row_rev.id) { tracing::error!("The row: {} should be in the no status group", row_rev.id); } - Ok(vec![GroupChangesetPB::delete( + Ok(vec![GroupRowsNotificationPB::delete( no_status_group.id.clone(), vec![row_rev.id.clone()], )]) @@ -273,7 +273,7 @@ where } #[tracing::instrument(level = "trace", skip_all, err)] - fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult> { + fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult> { let cell_rev = match context.row_rev.cells.get(&self.field_id) { Some(cell_rev) => Some(cell_rev.clone()), None => self.default_cell_rev(), diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs index 3187b216f4..d021847eea 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB}; +use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB}; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; use crate::services::group::action::GroupControllerCustomActions; use crate::services::group::configuration::GroupContext; @@ -8,9 +8,7 @@ use crate::services::group::controller::{ use crate::services::cell::insert_checkbox_cell; use crate::services::group::{move_group_row, GeneratedGroupConfig, GeneratedGroupContext}; -use flowy_grid_data_model::revision::{ - CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision, -}; +use grid_rev_model::{CellRevision, CheckboxGroupConfigurationRevision, FieldRevision, GroupRevision, RowRevision}; pub type CheckboxGroupController = GenericGroupController< CheckboxGroupConfigurationRevision, @@ -39,10 +37,10 @@ impl GroupControllerCustomActions for CheckboxGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, - ) -> Vec { + ) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_status_groups(|group| { - let mut changeset = GroupChangesetPB::new(group.id.clone()); + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let is_not_contained = !group.contains_row(&row_rev.id); if group.id == CHECK { if cell_data.is_uncheck() { @@ -81,10 +79,10 @@ impl GroupControllerCustomActions for CheckboxGroupController { changesets } - fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec { + fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_groups(|group| { - let mut changeset = GroupChangesetPB::new(group.id.clone()); + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); if group.contains_row(&row_rev.id) { changeset.deleted_rows.push(row_rev.id.clone()); group.remove_row(&row_rev.id); @@ -97,7 +95,11 @@ impl GroupControllerCustomActions for CheckboxGroupController { changesets } - fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { + fn move_row( + &mut self, + _cell_data: &Self::CellDataType, + mut context: MoveGroupRowContext, + ) -> Vec { let mut group_changeset = vec![]; self.group_ctx.iter_mut_groups(|group| { if let Some(changeset) = move_group_row(group, &mut context) { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs index d262d625c1..47b536c443 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs @@ -1,8 +1,8 @@ -use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB}; +use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB}; use crate::services::group::action::GroupControllerSharedActions; use crate::services::group::{Group, GroupController, MoveGroupRowContext}; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use grid_rev_model::{FieldRevision, RowRevision}; use std::sync::Arc; /// A [DefaultGroupController] is used to handle the group actions for the [FieldType] that doesn't @@ -36,8 +36,8 @@ impl GroupControllerSharedActions for DefaultGroupController { &self.field_id } - fn groups(&self) -> Vec { - vec![self.group.clone()] + fn groups(&self) -> Vec<&Group> { + vec![&self.group] } fn get_group(&self, _group_id: &str) -> Option<(usize, Group)> { @@ -59,7 +59,7 @@ impl GroupControllerSharedActions for DefaultGroupController { &mut self, _row_rev: &RowRevision, _field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { Ok(vec![]) } @@ -67,11 +67,11 @@ impl GroupControllerSharedActions for DefaultGroupController { &mut self, _row_rev: &RowRevision, _field_rev: &FieldRevision, - ) -> FlowyResult> { + ) -> FlowyResult> { Ok(vec![]) } - fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult> { + fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult> { todo!() } diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs index 58bb4a23d0..298e05affa 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupChangesetPB, RowPB}; +use crate::entities::{GroupRowsNotificationPB, RowPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; use crate::services::group::action::GroupControllerCustomActions; @@ -9,7 +9,7 @@ use crate::services::group::controller::{ use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::{make_no_status_group, GeneratedGroupContext}; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; +use grid_rev_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; // MultiSelect pub type MultiSelectGroupController = GenericGroupController< @@ -30,7 +30,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, - ) -> Vec { + ) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_status_groups(|group| { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { @@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController { changesets } - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_status_groups(|group| { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { @@ -50,7 +50,11 @@ impl GroupControllerCustomActions for MultiSelectGroupController { changesets } - fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { + fn move_row( + &mut self, + _cell_data: &Self::CellDataType, + mut context: MoveGroupRowContext, + ) -> Vec { let mut group_changeset = vec![]; self.group_ctx.iter_mut_groups(|group| { if let Some(changeset) = move_group_row(group, &mut context) { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs index 203e4f1675..0f3f671c8f 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs @@ -1,4 +1,4 @@ -use crate::entities::{GroupChangesetPB, RowPB}; +use crate::entities::{GroupRowsNotificationPB, RowPB}; use crate::services::cell::insert_select_option_cell; use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; use crate::services::group::action::GroupControllerCustomActions; @@ -10,7 +10,7 @@ use crate::services::group::controller_impls::select_option_controller::util::*; use crate::services::group::entities::Group; use crate::services::group::{make_no_status_group, GeneratedGroupContext}; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; +use grid_rev_model::{FieldRevision, RowRevision, SelectOptionGroupConfigurationRevision}; // SingleSelect pub type SingleSelectGroupController = GenericGroupController< @@ -30,7 +30,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController { &mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType, - ) -> Vec { + ) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_status_groups(|group| { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { @@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController { changesets } - fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { + fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec { let mut changesets = vec![]; self.group_ctx.iter_mut_status_groups(|group| { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { @@ -50,7 +50,11 @@ impl GroupControllerCustomActions for SingleSelectGroupController { changesets } - fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec { + fn move_row( + &mut self, + _cell_data: &Self::CellDataType, + mut context: MoveGroupRowContext, + ) -> Vec { let mut group_changeset = vec![]; self.group_ctx.iter_mut_groups(|group| { if let Some(changeset) = move_group_row(group, &mut context) { diff --git a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs index 29bbd8f0f9..7772ba3624 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs @@ -1,12 +1,10 @@ -use crate::entities::{FieldType, GroupChangesetPB, InsertedRowPB, RowPB}; +use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowPB}; use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell}; use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK}; use crate::services::group::configuration::GroupContext; use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::{GeneratedGroupConfig, Group}; -use flowy_grid_data_model::revision::{ - CellRevision, FieldRevision, GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision, -}; +use grid_rev_model::{CellRevision, FieldRevision, GroupRevision, RowRevision, SelectOptionGroupConfigurationRevision}; pub type SelectOptionGroupContext = GroupContext; @@ -14,8 +12,8 @@ pub fn add_or_remove_select_option_row( group: &mut Group, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, -) -> Option { - let mut changeset = GroupChangesetPB::new(group.id.clone()); +) -> Option { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); if cell_data.select_options.is_empty() { if group.contains_row(&row_rev.id) { changeset.deleted_rows.push(row_rev.id.clone()); @@ -47,8 +45,8 @@ pub fn remove_select_option_row( group: &mut Group, cell_data: &SelectOptionCellDataPB, row_rev: &RowRevision, -) -> Option { - let mut changeset = GroupChangesetPB::new(group.id.clone()); +) -> Option { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); cell_data.select_options.iter().for_each(|option| { if option.id == group.id && group.contains_row(&row_rev.id) { changeset.deleted_rows.push(row_rev.id.clone()); @@ -63,8 +61,8 @@ pub fn remove_select_option_row( } } -pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option { - let mut changeset = GroupChangesetPB::new(group.id.clone()); +pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option { + let mut changeset = GroupRowsNotificationPB::new(group.id.clone()); let MoveGroupRowContext { row_rev, row_changeset, diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs index c8116d6897..eb224ce493 100644 --- a/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs +++ b/frontend/rust-lib/flowy-grid/src/services/group/group_util.rs @@ -6,7 +6,7 @@ use crate::services::group::{ MultiSelectGroupController, SelectOptionGroupContext, SingleSelectGroupController, }; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{ +use grid_rev_model::{ CheckboxGroupConfigurationRevision, DateGroupConfigurationRevision, FieldRevision, GroupConfigurationRevision, GroupRevision, LayoutRevision, NumberGroupConfigurationRevision, RowRevision, SelectOptionGroupConfigurationRevision, TextGroupConfigurationRevision, UrlGroupConfigurationRevision, diff --git a/frontend/rust-lib/flowy-grid/src/services/mod.rs b/frontend/rust-lib/flowy-grid/src/services/mod.rs index ab864c544a..f69805e802 100644 --- a/frontend/rust-lib/flowy-grid/src/services/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/mod.rs @@ -1,19 +1,14 @@ mod util; pub mod block_editor; -mod block_manager; -mod block_manager_trait_impl; +pub mod block_manager; pub mod cell; pub mod field; -mod filter; +pub mod filter; pub mod grid_editor; -mod grid_editor_task; mod grid_editor_trait_impl; -pub mod grid_view_editor; -pub mod grid_view_manager; pub mod group; pub mod persistence; pub mod row; pub mod setting; -mod snapshot; -pub mod tasks; +pub mod view_editor; diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs index 3084432b95..c651571505 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/migration.rs @@ -1,15 +1,14 @@ use crate::manager::GridUser; +use crate::services::persistence::rev_sqlite::SQLiteGridRevisionPersistence; use crate::services::persistence::GridDatabase; use bytes::Bytes; use flowy_database::kv::KV; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::GridRevision; -use flowy_revision::disk::SQLiteGridRevisionPersistence; +use flowy_http_model::revision::Revision; +use flowy_http_model::util::md5; use flowy_revision::reset::{RevisionResettable, RevisionStructReset}; -use flowy_sync::client_grid::{make_grid_rev_json_str, GridRevisionPad}; -use flowy_sync::entities::revision::Revision; -use flowy_sync::util::md5; -use lib_ot::core::DeltaBuilder; +use flowy_sync::client_grid::{make_grid_rev_json_str, GridOperationsBuilder, GridRevisionPad}; +use grid_rev_model::GridRevision; use std::sync::Arc; const V1_MIGRATION: &str = "GRID_V1_MIGRATION"; @@ -64,7 +63,7 @@ impl RevisionResettable for GridRevisionResettable { fn reset_data(&self, revisions: Vec) -> FlowyResult { let pad = GridRevisionPad::from_revisions(revisions)?; let json = pad.json_str()?; - let bytes = DeltaBuilder::new().insert(&json).build().json_bytes(); + let bytes = GridOperationsBuilder::new().insert(&json).build().json_bytes(); Ok(bytes) } @@ -73,4 +72,12 @@ impl RevisionResettable for GridRevisionResettable { let json = make_grid_rev_json_str(&grid_rev)?; Ok(json) } + + fn read_record(&self) -> Option { + KV::get_str(self.target_id()) + } + + fn set_record(&self, record: String) { + KV::set_str(self.target_id(), record); + } } diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs index 7bd196acc7..5b3f7d1271 100644 --- a/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/mod.rs @@ -5,6 +5,7 @@ use std::sync::Arc; pub mod block_index; pub mod kv; pub mod migration; +pub mod rev_sqlite; pub trait GridDatabase: Send + Sync { fn db_pool(&self) -> Result, FlowyError>; diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_impl.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_block_impl.rs similarity index 88% rename from frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_impl.rs rename to frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_block_impl.rs index a12c5c1ad5..9808973c10 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_block_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_block_impl.rs @@ -1,5 +1,3 @@ -use crate::cache::disk::RevisionDiskCache; -use crate::disk::{RevisionChangeset, RevisionRecord}; use bytes::Bytes; use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_database::{ @@ -9,10 +7,9 @@ use flowy_database::{ ConnectionPool, }; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_sync::{ - entities::revision::{Revision, RevisionRange}, - util::md5, -}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use std::sync::Arc; pub struct SQLiteGridBlockRevisionPersistence { @@ -20,20 +17,24 @@ pub struct SQLiteGridBlockRevisionPersistence { pub(crate) pool: Arc, } -impl RevisionDiskCache for SQLiteGridBlockRevisionPersistence { +impl RevisionDiskCache> for SQLiteGridBlockRevisionPersistence { type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let _ = GridMetaRevisionSql::create(revision_records, &*conn)?; Ok(()) } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + fn read_revision_records( &self, object_id: &str, rev_ids: Option>, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let records = GridMetaRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; Ok(records) @@ -43,7 +44,7 @@ impl RevisionDiskCache for SQLiteGridBlockRevisionPersistence { &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = &*self.pool.get().map_err(internal_error)?; let revisions = GridMetaRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; Ok(revisions) @@ -70,7 +71,7 @@ impl RevisionDiskCache for SQLiteGridBlockRevisionPersistence { &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; conn.immediate_transaction::<_, FlowyError, _>(|| { @@ -92,7 +93,7 @@ impl SQLiteGridBlockRevisionPersistence { struct GridMetaRevisionSql(); impl GridMetaRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { // Batch insert: https://diesel.rs/guides/all-about-inserts.html let records = revision_records @@ -139,7 +140,7 @@ impl GridMetaRevisionSql { object_id: &str, rev_ids: Option>, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let mut sql = dsl::grid_meta_rev_table .filter(dsl::object_id.eq(object_id)) .into_boxed(); @@ -160,7 +161,7 @@ impl GridMetaRevisionSql { object_id: &str, range: RevisionRange, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let rev_tables = dsl::grid_meta_rev_table .filter(dsl::rev_id.ge(range.start)) .filter(dsl::rev_id.le(range.end)) @@ -216,17 +217,16 @@ impl std::default::Default for GridBlockRevisionState { } } -fn mk_revision_record_from_table(user_id: &str, table: GridBlockRevisionTable) -> RevisionRecord { +fn mk_revision_record_from_table(_user_id: &str, table: GridBlockRevisionTable) -> SyncRecord { let md5 = md5(&table.data); let revision = Revision::new( &table.object_id, table.base_rev_id, table.rev_id, Bytes::from(table.data), - user_id, md5, ); - RevisionRecord { + SyncRecord { revision, state: table.state.into(), write_to_disk: false, diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_impl.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_impl.rs similarity index 88% rename from frontend/rust-lib/flowy-revision/src/cache/disk/grid_impl.rs rename to frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_impl.rs index aef127baea..ddecdd04df 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_impl.rs @@ -1,5 +1,3 @@ -use crate::cache::disk::RevisionDiskCache; -use crate::disk::{RevisionChangeset, RevisionRecord}; use bytes::Bytes; use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_database::{ @@ -9,10 +7,9 @@ use flowy_database::{ ConnectionPool, }; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_sync::{ - entities::revision::{Revision, RevisionRange}, - util::md5, -}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use std::sync::Arc; pub struct SQLiteGridRevisionPersistence { @@ -20,20 +17,24 @@ pub struct SQLiteGridRevisionPersistence { pub(crate) pool: Arc, } -impl RevisionDiskCache for SQLiteGridRevisionPersistence { +impl RevisionDiskCache> for SQLiteGridRevisionPersistence { type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let _ = GridRevisionSql::create(revision_records, &*conn)?; Ok(()) } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + fn read_revision_records( &self, object_id: &str, rev_ids: Option>, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let records = GridRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; Ok(records) @@ -43,7 +44,7 @@ impl RevisionDiskCache for SQLiteGridRevisionPersistence { &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = &*self.pool.get().map_err(internal_error)?; let revisions = GridRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; Ok(revisions) @@ -70,7 +71,7 @@ impl RevisionDiskCache for SQLiteGridRevisionPersistence { &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; conn.immediate_transaction::<_, FlowyError, _>(|| { @@ -92,7 +93,7 @@ impl SQLiteGridRevisionPersistence { struct GridRevisionSql(); impl GridRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { // Batch insert: https://diesel.rs/guides/all-about-inserts.html let records = revision_records .into_iter() @@ -138,7 +139,7 @@ impl GridRevisionSql { object_id: &str, rev_ids: Option>, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let mut sql = dsl::grid_rev_table.filter(dsl::object_id.eq(object_id)).into_boxed(); if let Some(rev_ids) = rev_ids { sql = sql.filter(dsl::rev_id.eq_any(rev_ids)); @@ -157,7 +158,7 @@ impl GridRevisionSql { object_id: &str, range: RevisionRange, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let rev_tables = dsl::grid_rev_table .filter(dsl::rev_id.ge(range.start)) .filter(dsl::rev_id.le(range.end)) @@ -214,17 +215,16 @@ impl std::default::Default for GridRevisionState { } } -fn mk_revision_record_from_table(user_id: &str, table: GridRevisionTable) -> RevisionRecord { +fn mk_revision_record_from_table(_user_id: &str, table: GridRevisionTable) -> SyncRecord { let md5 = md5(&table.data); let revision = Revision::new( &table.object_id, table.base_rev_id, table.rev_id, Bytes::from(table.data), - user_id, md5, ); - RevisionRecord { + SyncRecord { revision, state: table.state.into(), write_to_disk: false, diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_view_impl.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_view_impl.rs similarity index 88% rename from frontend/rust-lib/flowy-revision/src/cache/disk/grid_view_impl.rs rename to frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_view_impl.rs index 71d923e046..bb2fae0634 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/grid_view_impl.rs +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/grid_view_impl.rs @@ -1,4 +1,3 @@ -use crate::disk::{RevisionChangeset, RevisionDiskCache, RevisionRecord}; use bytes::Bytes; use diesel::{sql_types::Integer, update, SqliteConnection}; use flowy_database::{ @@ -8,10 +7,9 @@ use flowy_database::{ ConnectionPool, }; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_sync::{ - entities::revision::{Revision, RevisionRange}, - util::md5, -}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, RevisionState, SyncRecord}; use std::sync::Arc; pub struct SQLiteGridViewRevisionPersistence { @@ -28,20 +26,24 @@ impl SQLiteGridViewRevisionPersistence { } } -impl RevisionDiskCache for SQLiteGridViewRevisionPersistence { +impl RevisionDiskCache> for SQLiteGridViewRevisionPersistence { type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let _ = GridViewRevisionSql::create(revision_records, &*conn)?; Ok(()) } + fn get_connection(&self) -> Result, Self::Error> { + Ok(self.pool.clone()) + } + fn read_revision_records( &self, object_id: &str, rev_ids: Option>, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = self.pool.get().map_err(internal_error)?; let records = GridViewRevisionSql::read(&self.user_id, object_id, rev_ids, &*conn)?; Ok(records) @@ -51,7 +53,7 @@ impl RevisionDiskCache for SQLiteGridViewRevisionPersistence { &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { let conn = &*self.pool.get().map_err(internal_error)?; let revisions = GridViewRevisionSql::read_with_range(&self.user_id, object_id, range.clone(), conn)?; Ok(revisions) @@ -78,7 +80,7 @@ impl RevisionDiskCache for SQLiteGridViewRevisionPersistence { &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error> { let conn = self.pool.get().map_err(internal_error)?; conn.immediate_transaction::<_, FlowyError, _>(|| { @@ -91,7 +93,7 @@ impl RevisionDiskCache for SQLiteGridViewRevisionPersistence { struct GridViewRevisionSql(); impl GridViewRevisionSql { - fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { + fn create(revision_records: Vec, conn: &SqliteConnection) -> Result<(), FlowyError> { // Batch insert: https://diesel.rs/guides/all-about-inserts.html let records = revision_records .into_iter() @@ -137,7 +139,7 @@ impl GridViewRevisionSql { object_id: &str, rev_ids: Option>, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let mut sql = dsl::grid_view_rev_table .filter(dsl::object_id.eq(object_id)) .into_boxed(); @@ -158,7 +160,7 @@ impl GridViewRevisionSql { object_id: &str, range: RevisionRange, conn: &SqliteConnection, - ) -> Result, FlowyError> { + ) -> Result, FlowyError> { let rev_tables = dsl::grid_view_rev_table .filter(dsl::rev_id.ge(range.start)) .filter(dsl::rev_id.le(range.end)) @@ -215,17 +217,16 @@ impl std::default::Default for GridViewRevisionState { } } -fn mk_revision_record_from_table(user_id: &str, table: GridViewRevisionTable) -> RevisionRecord { +fn mk_revision_record_from_table(_user_id: &str, table: GridViewRevisionTable) -> SyncRecord { let md5 = md5(&table.data); let revision = Revision::new( &table.object_id, table.base_rev_id, table.rev_id, Bytes::from(table.data), - user_id, md5, ); - RevisionRecord { + SyncRecord { revision, state: table.state.into(), write_to_disk: false, diff --git a/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/mod.rs b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/mod.rs new file mode 100644 index 0000000000..05a19d3413 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/persistence/rev_sqlite/mod.rs @@ -0,0 +1,7 @@ +mod grid_block_impl; +mod grid_impl; +mod grid_view_impl; + +pub use grid_block_impl::*; +pub use grid_impl::*; +pub use grid_view_impl::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index a9bef408f1..ddf8c40675 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs @@ -3,7 +3,7 @@ use crate::services::cell::{ insert_url_cell, }; -use flowy_grid_data_model::revision::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; +use grid_rev_model::{gen_row_id, CellRevision, FieldRevision, RowRevision, DEFAULT_ROW_HEIGHT}; use indexmap::IndexMap; use std::collections::HashMap; use std::sync::Arc; diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 1a0d0eaff6..0b92889fca 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,10 +1,10 @@ use crate::entities::{BlockPB, RepeatedBlockPB, RowPB}; -use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::RowRevision; + +use grid_rev_model::RowRevision; use std::collections::HashMap; use std::sync::Arc; -pub struct GridBlockSnapshot { +pub struct GridBlock { pub(crate) block_id: String, pub row_revs: Vec>, } @@ -35,7 +35,7 @@ pub(crate) fn block_from_row_orders(row_orders: Vec) -> Vec { // Some((field_id, cell)) // } -pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc]) -> Vec { +pub(crate) fn make_row_pb_from_row_rev(row_revs: &[Arc]) -> Vec { row_revs.iter().map(RowPB::from).collect::>() } @@ -53,36 +53,13 @@ pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec>() } -pub(crate) fn make_grid_blocks( - block_ids: Option>, - block_snapshots: Vec, -) -> FlowyResult { - match block_ids { - None => Ok(block_snapshots - .into_iter() - .map(|snapshot| { - let row_orders = make_row_orders_from_row_revs(&snapshot.row_revs); - BlockPB::new(&snapshot.block_id, row_orders) - }) - .collect::>() - .into()), - Some(block_ids) => { - let block_meta_data_map: HashMap<&String, &Vec>> = block_snapshots - .iter() - .map(|data| (&data.block_id, &data.row_revs)) - .collect(); - - let mut grid_blocks = vec![]; - for block_id in block_ids { - match block_meta_data_map.get(&block_id) { - None => {} - Some(row_revs) => { - let row_orders = make_row_orders_from_row_revs(row_revs); - grid_blocks.push(BlockPB::new(&block_id, row_orders)); - } - } - } - Ok(grid_blocks.into()) - } - } +pub(crate) fn make_block_pbs(blocks: Vec) -> RepeatedBlockPB { + blocks + .into_iter() + .map(|block| { + let row_pbs = make_row_pb_from_row_rev(&block.row_revs); + BlockPB::new(&block.block_id, row_pbs) + }) + .collect::>() + .into() } diff --git a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs index 2bf8f1bbeb..16ee630cc7 100644 --- a/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/setting/setting_builder.rs @@ -1,4 +1,4 @@ -use crate::entities::{DeleteFilterParams, GridLayout, GridSettingChangesetParams, InsertFilterParams}; +use crate::entities::{CreateFilterParams, DeleteFilterParams, GridLayout, GridSettingChangesetParams}; pub struct GridSettingChangesetBuilder { params: GridSettingChangesetParams, @@ -17,7 +17,7 @@ impl GridSettingChangesetBuilder { Self { params } } - pub fn insert_filter(mut self, params: InsertFilterParams) -> Self { + pub fn insert_filter(mut self, params: CreateFilterParams) -> Self { self.params.insert_filter = Some(params); self } diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs deleted file mode 100644 index 9c00ccc85f..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/snapshot/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod snapshot_service; - -pub use snapshot_service::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs b/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs deleted file mode 100644 index 2a47b70f48..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/snapshot/snapshot_service.rs +++ /dev/null @@ -1,7 +0,0 @@ -// pub struct GridSnapshotService {} -// -// impl GridSnapshotService { -// pub fn new() -> Self { -// Self {} -// } -// } diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs deleted file mode 100644 index 121329edfb..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/runner.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::services::tasks::scheduler::GridTaskScheduler; - -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{watch, RwLock}; -use tokio::time::interval; - -pub struct GridTaskRunner { - scheduler: Arc>, - debounce_duration: Duration, - notifier: Option>, -} - -impl GridTaskRunner { - pub fn new( - scheduler: Arc>, - notifier: watch::Receiver, - debounce_duration: Duration, - ) -> Self { - Self { - scheduler, - debounce_duration, - notifier: Some(notifier), - } - } - - pub async fn run(mut self) { - let mut notifier = self - .notifier - .take() - .expect("The GridTaskRunner's notifier should only take once"); - - loop { - if notifier.changed().await.is_err() { - // The runner will be stopped if the corresponding Sender drop. - break; - } - - if *notifier.borrow() { - break; - } - let mut interval = interval(self.debounce_duration); - interval.tick().await; - let _ = self.scheduler.write().await.process_next_task().await; - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs deleted file mode 100644 index 73ba298d9b..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/scheduler.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::services::tasks::queue::{GridTaskQueue, TaskHandlerId}; -use crate::services::tasks::runner::GridTaskRunner; -use crate::services::tasks::store::GridTaskStore; -use crate::services::tasks::task::Task; - -use crate::services::tasks::{TaskContent, TaskId, TaskStatus}; -use flowy_error::FlowyError; -use lib_infra::future::BoxResultFuture; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::{watch, RwLock}; - -pub(crate) trait GridTaskHandler: Send + Sync + 'static { - fn handler_id(&self) -> &str; - - fn process_content(&self, content: TaskContent) -> BoxResultFuture<(), FlowyError>; -} - -pub struct GridTaskScheduler { - queue: GridTaskQueue, - store: GridTaskStore, - notifier: watch::Sender, - handlers: HashMap>, -} - -impl GridTaskScheduler { - pub(crate) fn new() -> Arc> { - let (notifier, rx) = watch::channel(false); - - let scheduler = Self { - queue: GridTaskQueue::new(), - store: GridTaskStore::new(), - notifier, - handlers: HashMap::new(), - }; - // The runner will receive the newest value after start running. - scheduler.notify(); - - let scheduler = Arc::new(RwLock::new(scheduler)); - let debounce_duration = Duration::from_millis(300); - let runner = GridTaskRunner::new(scheduler.clone(), rx, debounce_duration); - tokio::spawn(runner.run()); - - scheduler - } - - pub(crate) fn register_handler(&mut self, handler: Arc) - where - T: GridTaskHandler, - { - let handler_id = handler.handler_id().to_owned(); - self.handlers.insert(handler_id, handler); - } - - pub(crate) fn unregister_handler>(&mut self, handler_id: T) { - let _ = self.handlers.remove(handler_id.as_ref()); - } - - #[allow(dead_code)] - pub(crate) fn stop(&mut self) { - let _ = self.notifier.send(true); - self.queue.clear(); - self.store.clear(); - } - - pub(crate) async fn process_next_task(&mut self) -> Option<()> { - let pending_task = self.queue.mut_head(|list| list.pop())?; - let mut task = self.store.remove_task(&pending_task.id)?; - let handler = self.handlers.get(&task.handler_id)?; - - let ret = task.ret.take()?; - let content = task.content.take()?; - - task.set_status(TaskStatus::Processing); - let _ = match handler.process_content(content).await { - Ok(_) => { - task.set_status(TaskStatus::Done); - let _ = ret.send(task.into()); - } - Err(e) => { - tracing::error!("Process task failed: {:?}", e); - task.set_status(TaskStatus::Failure); - let _ = ret.send(task.into()); - } - }; - self.notify(); - None - } - - pub(crate) fn add_task(&mut self, task: Task) { - assert!(!task.is_finished()); - self.queue.push(&task); - self.store.insert_task(task); - self.notify(); - } - - pub(crate) fn next_task_id(&self) -> TaskId { - self.store.next_task_id() - } - - pub(crate) fn notify(&self) { - let _ = self.notifier.send(false); - } -} - -#[cfg(test)] -mod tests { - use crate::services::grid_editor_task::GridServiceTaskScheduler; - use crate::services::tasks::{GridTaskHandler, GridTaskScheduler, Task, TaskContent, TaskStatus}; - use flowy_error::FlowyError; - use lib_infra::future::BoxResultFuture; - use std::sync::Arc; - use std::time::Duration; - use tokio::time::interval; - - #[tokio::test] - async fn task_scheduler_snapshot_task_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task = Task::new("1", task_id, TaskContent::Snapshot); - let rx = task.rx.take().unwrap(); - scheduler.write().await.add_task(task); - assert_eq!(rx.await.unwrap().status, TaskStatus::Done); - } - - #[tokio::test] - async fn task_scheduler_snapshot_task_cancel_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task = Task::new("1", task_id, TaskContent::Snapshot); - let rx = task.rx.take().unwrap(); - scheduler.write().await.add_task(task); - scheduler.write().await.stop(); - - assert_eq!(rx.await.unwrap().status, TaskStatus::Cancel); - } - - #[tokio::test] - async fn task_scheduler_multi_task_test() { - let scheduler = GridTaskScheduler::new(); - scheduler - .write() - .await - .register_handler(Arc::new(MockGridTaskHandler())); - - let task_id = scheduler.gen_task_id().await; - let mut task_1 = Task::new("1", task_id, TaskContent::Snapshot); - let rx_1 = task_1.rx.take().unwrap(); - - let task_id = scheduler.gen_task_id().await; - let mut task_2 = Task::new("1", task_id, TaskContent::Snapshot); - let rx_2 = task_2.rx.take().unwrap(); - - scheduler.write().await.add_task(task_1); - scheduler.write().await.add_task(task_2); - - assert_eq!(rx_1.await.unwrap().status, TaskStatus::Done); - assert_eq!(rx_2.await.unwrap().status, TaskStatus::Done); - } - struct MockGridTaskHandler(); - impl GridTaskHandler for MockGridTaskHandler { - fn handler_id(&self) -> &str { - "1" - } - - fn process_content(&self, _content: TaskContent) -> BoxResultFuture<(), FlowyError> { - Box::pin(async move { - let mut interval = interval(Duration::from_secs(1)); - interval.tick().await; - interval.tick().await; - Ok(()) - }) - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs b/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs deleted file mode 100644 index 6b88e4598f..0000000000 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/task.rs +++ /dev/null @@ -1,129 +0,0 @@ -#![allow(clippy::all)] -#![allow(dead_code)] -use crate::services::row::GridBlockSnapshot; -use crate::services::tasks::queue::TaskHandlerId; -use std::cmp::Ordering; - -#[derive(Eq, Debug, Clone, Copy)] -pub enum TaskType { - /// Remove the row if it doesn't satisfy the filter. - Filter, - /// Generate snapshot for grid, unused by now. - Snapshot, - - Group, -} - -impl PartialEq for TaskType { - fn eq(&self, other: &Self) -> bool { - matches!( - (self, other), - (Self::Filter, Self::Filter) | (Self::Snapshot, Self::Snapshot) - ) - } -} - -pub type TaskId = u32; - -#[derive(Eq, Debug, Clone, Copy)] -pub struct PendingTask { - pub ty: TaskType, - pub id: TaskId, -} - -impl PartialEq for PendingTask { - fn eq(&self, other: &Self) -> bool { - self.id.eq(&other.id) - } -} - -impl PartialOrd for PendingTask { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PendingTask { - fn cmp(&self, other: &Self) -> Ordering { - match (self.ty, other.ty) { - // Snapshot - (TaskType::Snapshot, TaskType::Snapshot) => Ordering::Equal, - (TaskType::Snapshot, _) => Ordering::Greater, - (_, TaskType::Snapshot) => Ordering::Less, - // Group - (TaskType::Group, TaskType::Group) => self.id.cmp(&other.id).reverse(), - (TaskType::Group, _) => Ordering::Greater, - (_, TaskType::Group) => Ordering::Greater, - // Filter - (TaskType::Filter, TaskType::Filter) => self.id.cmp(&other.id).reverse(), - } - } -} - -pub(crate) struct FilterTaskContext { - pub blocks: Vec, -} - -pub(crate) enum TaskContent { - #[allow(dead_code)] - Snapshot, - Group, - Filter(FilterTaskContext), -} - -#[derive(Debug, Eq, PartialEq)] -pub(crate) enum TaskStatus { - Pending, - Processing, - Done, - Failure, - Cancel, -} - -pub(crate) struct Task { - pub id: TaskId, - pub handler_id: TaskHandlerId, - pub content: Option, - status: TaskStatus, - pub ret: Option>, - pub rx: Option>, -} - -pub(crate) struct TaskResult { - pub id: TaskId, - pub(crate) status: TaskStatus, -} - -impl std::convert::From for TaskResult { - fn from(task: Task) -> Self { - TaskResult { - id: task.id, - status: task.status, - } - } -} - -impl Task { - pub fn new(handler_id: &str, id: TaskId, content: TaskContent) -> Self { - let (ret, rx) = tokio::sync::oneshot::channel(); - Self { - handler_id: handler_id.to_owned(), - id, - content: Some(content), - ret: Some(ret), - rx: Some(rx), - status: TaskStatus::Pending, - } - } - - pub fn set_status(&mut self, status: TaskStatus) { - self.status = status; - } - - pub fn is_finished(&self) -> bool { - match self.status { - TaskStatus::Done => true, - _ => false, - } - } -} diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/changed_notifier.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/changed_notifier.rs new file mode 100644 index 0000000000..3a58432918 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/view_editor/changed_notifier.rs @@ -0,0 +1,46 @@ +use crate::dart_notification::{send_dart_notification, GridDartNotification}; +use crate::entities::GridBlockChangesetPB; +use crate::services::filter::FilterResultNotification; +use async_stream::stream; +use futures::stream::StreamExt; +use tokio::sync::broadcast; + +#[derive(Clone)] +pub enum GridViewChanged { + DidReceiveFilterResult(FilterResultNotification), +} + +pub type GridViewChangedNotifier = broadcast::Sender; + +pub(crate) struct GridViewChangedReceiverRunner(pub(crate) Option>); +impl GridViewChangedReceiverRunner { + pub(crate) async fn run(mut self) { + let mut receiver = self.0.take().expect("Only take once"); + let stream = stream! { + loop { + match receiver.recv().await { + Ok(changed) => yield changed, + Err(_e) => break, + } + } + }; + stream + .for_each(|changed| async { + match changed { + GridViewChanged::DidReceiveFilterResult(notification) => { + let changeset = GridBlockChangesetPB { + block_id: notification.block_id, + visible_rows: notification.visible_rows, + invisible_rows: notification.invisible_rows, + ..Default::default() + }; + + send_dart_notification(&changeset.block_id, GridDartNotification::DidUpdateGridBlock) + .payload(changeset) + .send() + } + } + }) + .await; + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs new file mode 100644 index 0000000000..93dcae3f09 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor.rs @@ -0,0 +1,575 @@ +use crate::dart_notification::{send_dart_notification, GridDartNotification}; +use crate::entities::*; +use crate::services::filter::{FilterChangeset, FilterController, FilterTaskHandler, FilterType}; + +use crate::services::group::{ + default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader, + GroupController, MoveGroupRowContext, +}; +use crate::services::row::GridBlock; +use crate::services::view_editor::changed_notifier::GridViewChangedNotifier; +use crate::services::view_editor::trait_impl::*; +use flowy_database::ConnectionPool; +use flowy_error::FlowyResult; +use flowy_revision::RevisionManager; +use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; +use flowy_task::TaskDispatcher; +use grid_rev_model::{gen_grid_filter_id, FieldRevision, FieldTypeRevision, FilterRevision, RowChangeset, RowRevision}; +use lib_infra::future::Fut; +use lib_infra::ref_map::RefCountValue; +use nanoid::nanoid; +use std::future::Future; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub trait GridViewEditorDelegate: Send + Sync + 'static { + /// If the field_ids is None, then it will return all the field revisions + fn get_field_revs(&self, field_ids: Option>) -> Fut>>; + fn get_field_rev(&self, field_id: &str) -> Fut>>; + + fn index_of_row(&self, row_id: &str) -> Fut>; + fn get_row_rev(&self, row_id: &str) -> Fut>>; + fn get_row_revs(&self) -> Fut>>; + fn get_blocks(&self) -> Fut>; + + fn get_task_scheduler(&self) -> Arc>; +} + +pub struct GridViewRevisionEditor { + user_id: String, + view_id: String, + pad: Arc>, + rev_manager: Arc>>, + delegate: Arc, + group_controller: Arc>>, + filter_controller: Arc>, +} + +impl GridViewRevisionEditor { + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn new( + user_id: &str, + token: &str, + view_id: String, + delegate: Arc, + notifier: GridViewChangedNotifier, + mut rev_manager: RevisionManager>, + ) -> FlowyResult { + let cloud = Arc::new(GridViewRevisionCloudService { + token: token.to_owned(), + }); + let view_revision_pad = rev_manager.initialize::(Some(cloud)).await?; + let pad = Arc::new(RwLock::new(view_revision_pad)); + let rev_manager = Arc::new(rev_manager); + let group_controller = new_group_controller( + user_id.to_owned(), + view_id.clone(), + pad.clone(), + rev_manager.clone(), + delegate.clone(), + ) + .await?; + + let user_id = user_id.to_owned(); + let group_controller = Arc::new(RwLock::new(group_controller)); + let filter_controller = make_filter_controller(&view_id, delegate.clone(), notifier.clone(), pad.clone()).await; + Ok(Self { + pad, + user_id, + view_id, + rev_manager, + delegate, + group_controller, + filter_controller, + }) + } + + #[tracing::instrument(name = "close grid view editor", level = "trace", skip_all)] + pub fn close(&self) { + let filter_controller = self.filter_controller.clone(); + tokio::spawn(async move { + filter_controller.read().await.close().await; + }); + } + + pub async fn filter_rows(&self, _block_id: &str, mut rows: Vec>) -> Vec> { + self.filter_controller.write().await.filter_row_revs(&mut rows).await; + rows + } + + pub async fn duplicate_view_data(&self) -> FlowyResult { + let json_str = self.pad.read().await.json_str()?; + Ok(json_str) + } + + pub async fn will_create_view_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + if params.group_id.is_none() { + return; + } + let group_id = params.group_id.as_ref().unwrap(); + let _ = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.will_create_row(row_rev, &field_rev, group_id); + Ok(()) + }) + .await; + } + + pub async fn did_create_view_row(&self, row_pb: &RowPB, params: &CreateRowParams) { + // Send the group notification if the current view has groups + match params.group_id.as_ref() { + None => {} + Some(group_id) => { + let index = match params.start_row_id { + None => Some(0), + Some(_) => None, + }; + + self.group_controller.write().await.did_create_row(row_pb, group_id); + let inserted_row = InsertedRowPB { + row: row_pb.clone(), + index, + is_new: true, + }; + let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]); + self.notify_did_update_group_rows(changeset).await; + } + } + } + + #[tracing::instrument(level = "trace", skip_all)] + pub async fn did_delete_view_row(&self, row_rev: &RowRevision) { + // Send the group notification if the current view has groups; + let changesets = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.did_delete_delete_row(row_rev, &field_rev) + }) + .await; + + tracing::trace!("Delete row in view changeset: {:?}", changesets); + if let Some(changesets) = changesets { + for changeset in changesets { + self.notify_did_update_group_rows(changeset).await; + } + } + } + + pub async fn did_update_view_cell(&self, row_rev: &RowRevision) { + let changesets = self + .mut_group_controller(|group_controller, field_rev| { + group_controller.did_update_group_row(row_rev, &field_rev) + }) + .await; + + if let Some(changesets) = changesets { + for changeset in changesets { + self.notify_did_update_group_rows(changeset).await; + } + } + } + + pub async fn move_view_group_row( + &self, + row_rev: &RowRevision, + row_changeset: &mut RowChangeset, + to_group_id: &str, + to_row_id: Option, + ) -> Vec { + let changesets = self + .mut_group_controller(|group_controller, field_rev| { + let move_row_context = MoveGroupRowContext { + row_rev, + row_changeset, + field_rev: field_rev.as_ref(), + to_group_id, + to_row_id, + }; + + let changesets = group_controller.move_group_row(move_row_context)?; + Ok(changesets) + }) + .await; + + changesets.unwrap_or_default() + } + /// Only call once after grid view editor initialized + #[tracing::instrument(level = "trace", skip(self))] + pub async fn load_view_groups(&self) -> FlowyResult> { + let groups = self + .group_controller + .read() + .await + .groups() + .into_iter() + .cloned() + .collect::>(); + tracing::trace!("Number of groups: {}", groups.len()); + Ok(groups.into_iter().map(GroupPB::from).collect()) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn move_view_group(&self, params: MoveGroupParams) -> FlowyResult<()> { + let _ = self + .group_controller + .write() + .await + .move_group(¶ms.from_group_id, ¶ms.to_group_id)?; + match self.group_controller.read().await.get_group(¶ms.from_group_id) { + None => tracing::warn!("Can not find the group with id: {}", params.from_group_id), + Some((index, group)) => { + let inserted_group = InsertedGroupPB { + group: GroupPB::from(group), + index: index as i32, + }; + + let changeset = GroupViewChangesetPB { + view_id: self.view_id.clone(), + inserted_groups: vec![inserted_group], + deleted_groups: vec![params.from_group_id.clone()], + update_groups: vec![], + new_groups: vec![], + }; + + self.notify_did_update_view(changeset).await; + } + } + Ok(()) + } + + pub async fn group_id(&self) -> String { + self.group_controller.read().await.field_id().to_string() + } + + pub async fn get_view_setting(&self) -> GridSettingPB { + let field_revs = self.delegate.get_field_revs(None).await; + let grid_setting = make_grid_setting(&*self.pad.read().await, &field_revs); + grid_setting + } + + pub async fn get_all_view_filters(&self) -> Vec> { + let field_revs = self.delegate.get_field_revs(None).await; + self.pad.read().await.get_all_filters(&field_revs) + } + + pub async fn get_view_filters(&self, filter_type: &FilterType) -> Vec> { + let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into(); + self.pad + .read() + .await + .get_filters(&filter_type.field_id, &field_type_rev) + } + + /// Initialize new group when grouping by a new field + /// + pub async fn initialize_new_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(¶ms.field_id).await { + let _ = self + .modify(|pad| { + let configuration = default_group_configuration(&field_rev); + let changeset = pad.insert_or_update_group_configuration( + ¶ms.field_id, + ¶ms.field_type_rev, + configuration, + )?; + Ok(changeset) + }) + .await?; + } + if self.group_controller.read().await.field_id() != params.field_id { + let _ = self.group_by_view_field(¶ms.field_id).await?; + self.notify_did_update_setting().await; + } + Ok(()) + } + + pub async fn delete_view_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + self.modify(|pad| { + let changeset = pad.delete_group(¶ms.group_id, ¶ms.field_id, ¶ms.field_type_rev)?; + Ok(changeset) + }) + .await + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { + let filter_type = FilterType::from(¶ms); + let filter_rev = FilterRevision { + id: gen_grid_filter_id(), + field_id: params.field_id.clone(), + field_type_rev: params.field_type_rev, + condition: params.condition, + content: params.content, + }; + let filter_pb = FilterPB::from(&filter_rev); + let _ = self + .modify(|pad| { + let changeset = pad.insert_filter(¶ms.field_id, ¶ms.field_type_rev, filter_rev)?; + Ok(changeset) + }) + .await?; + + self.filter_controller + .write() + .await + .apply_changeset(FilterChangeset::from_insert(filter_type)) + .await; + + let changeset = FilterChangesetNotificationPB::from_insert(&self.view_id, vec![filter_pb]); + self.notify_did_update_filter(changeset).await; + Ok(()) + } + + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let filter_type = params.filter_type; + let field_type_rev = filter_type.field_type_rev(); + let filters = self + .get_view_filters(&filter_type) + .await + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect(); + let _ = self + .modify(|pad| { + let changeset = pad.delete_filter(¶ms.filter_id, &filter_type.field_id, &field_type_rev)?; + Ok(changeset) + }) + .await?; + + self.filter_controller + .write() + .await + .apply_changeset(FilterChangeset::from_delete(filter_type)) + .await; + + let changeset = FilterChangesetNotificationPB::from_delete(&self.view_id, filters); + self.notify_did_update_filter(changeset).await; + Ok(()) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let filter_type = FilterType::from(&field_rev); + let filter_changeset = FilterChangeset::from_insert(filter_type); + self.filter_controller + .write() + .await + .apply_changeset(filter_changeset) + .await; + } + Ok(()) + } + + /// + /// + /// # Arguments + /// + /// * `field_id`: + /// + #[tracing::instrument(level = "debug", skip_all, err)] + pub async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> { + if let Some(field_rev) = self.delegate.get_field_rev(field_id).await { + let row_revs = self.delegate.get_row_revs().await; + let new_group_controller = new_group_controller_with_field_rev( + self.user_id.clone(), + self.view_id.clone(), + self.pad.clone(), + self.rev_manager.clone(), + field_rev, + row_revs, + ) + .await?; + + let new_groups = new_group_controller + .groups() + .into_iter() + .map(|group| GroupPB::from(group.clone())) + .collect(); + + *self.group_controller.write().await = new_group_controller; + let changeset = GroupViewChangesetPB { + view_id: self.view_id.clone(), + new_groups, + ..Default::default() + }; + + debug_assert!(!changeset.is_empty()); + if !changeset.is_empty() { + send_dart_notification(&changeset.view_id, GridDartNotification::DidGroupByNewField) + .payload(changeset) + .send(); + } + } + Ok(()) + } + + async fn notify_did_update_setting(&self) { + let setting = self.get_view_setting().await; + send_dart_notification(&self.view_id, GridDartNotification::DidUpdateGridSetting) + .payload(setting) + .send(); + } + + pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) { + send_dart_notification(&payload.group_id, GridDartNotification::DidUpdateGroup) + .payload(payload) + .send(); + } + + pub async fn notify_did_update_filter(&self, changeset: FilterChangesetNotificationPB) { + send_dart_notification(&changeset.view_id, GridDartNotification::DidUpdateFilter) + .payload(changeset) + .send(); + } + + async fn notify_did_update_view(&self, changeset: GroupViewChangesetPB) { + send_dart_notification(&self.view_id, GridDartNotification::DidUpdateGroupView) + .payload(changeset) + .send(); + } + + async fn modify(&self, f: F) -> FlowyResult<()> + where + F: for<'a> FnOnce(&'a mut GridViewRevisionPad) -> FlowyResult>, + { + let mut write_guard = self.pad.write().await; + match f(&mut *write_guard)? { + None => {} + Some(change) => { + let _ = apply_change(&self.user_id, self.rev_manager.clone(), change).await?; + } + } + Ok(()) + } + + async fn mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(&mut Box, Arc) -> FlowyResult, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let mut write_guard = self.group_controller.write().await; + f(&mut write_guard, field_rev).ok() + } + } + } + + #[allow(dead_code)] + async fn async_mut_group_controller(&self, f: F) -> Option + where + F: FnOnce(Arc>>, Arc) -> O, + O: Future> + Sync + 'static, + { + let group_field_id = self.group_controller.read().await.field_id().to_owned(); + match self.delegate.get_field_rev(&group_field_id).await { + None => None, + Some(field_rev) => { + let _write_guard = self.group_controller.write().await; + f(self.group_controller.clone(), field_rev).await.ok() + } + } + } +} + +impl RefCountValue for GridViewRevisionEditor { + fn did_remove(&self) { + self.close(); + } +} + +async fn new_group_controller( + user_id: String, + view_id: String, + view_rev_pad: Arc>, + rev_manager: Arc>>, + delegate: Arc, +) -> FlowyResult> { + let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); + let field_revs = delegate.get_field_revs(None).await; + let row_revs = delegate.get_row_revs().await; + let layout = view_rev_pad.read().await.layout(); + // Read the group field or find a new group field + let field_rev = configuration_reader + .get_configuration() + .await + .and_then(|configuration| { + field_revs + .iter() + .find(|field_rev| field_rev.id == configuration.field_id) + .cloned() + }) + .unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap()); + + new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_revs).await +} + +/// Returns a [GroupController] +/// +async fn new_group_controller_with_field_rev( + user_id: String, + view_id: String, + view_rev_pad: Arc>, + rev_manager: Arc>>, + field_rev: Arc, + row_revs: Vec>, +) -> FlowyResult> { + let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone()); + let configuration_writer = GroupConfigurationWriterImpl { + user_id, + rev_manager, + view_pad: view_rev_pad, + }; + make_group_controller(view_id, field_rev, row_revs, configuration_reader, configuration_writer).await +} + +async fn make_filter_controller( + view_id: &str, + delegate: Arc, + notifier: GridViewChangedNotifier, + pad: Arc>, +) -> Arc> { + let field_revs = delegate.get_field_revs(None).await; + let filter_revs = pad.read().await.get_all_filters(&field_revs); + let task_scheduler = delegate.get_task_scheduler(); + let filter_delegate = GridViewFilterDelegateImpl { + editor_delegate: delegate.clone(), + view_revision_pad: pad, + }; + let handler_id = gen_handler_id(); + let filter_controller = FilterController::new( + view_id, + &handler_id, + filter_delegate, + task_scheduler.clone(), + filter_revs, + notifier, + ) + .await; + let filter_controller = Arc::new(RwLock::new(filter_controller)); + task_scheduler + .write() + .await + .register_handler(FilterTaskHandler::new(handler_id, filter_controller.clone())); + filter_controller +} + +fn gen_handler_id() -> String { + nanoid!(10) +} + +#[cfg(test)] +mod tests { + use flowy_sync::client_grid::GridOperations; + + #[test] + fn test() { + let s1 = r#"[{"insert":"{\"view_id\":\"fTURELffPr\",\"grid_id\":\"fTURELffPr\",\"layout\":0,\"filters\":[],\"groups\":[]}"}]"#; + let _delta_1 = GridOperations::from_json(s1).unwrap(); + + let s2 = r#"[{"retain":195},{"insert":"{\\\"group_id\\\":\\\"wD9i\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"xZtv\\\",\\\"visible\\\":true},{\\\"group_id\\\":\\\"tFV2\\\",\\\"visible\\\":true}"},{"retain":10}]"#; + let _delta_2 = GridOperations::from_json(s2).unwrap(); + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs new file mode 100644 index 0000000000..d6e65a4963 --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/view_editor/editor_manager.rs @@ -0,0 +1,254 @@ +use crate::entities::{ + CreateFilterParams, CreateRowParams, DeleteFilterParams, DeleteGroupParams, GridSettingPB, InsertGroupParams, + MoveGroupParams, RepeatedGridGroupPB, RowPB, +}; +use crate::manager::GridUser; +use crate::services::filter::FilterType; +use crate::services::persistence::rev_sqlite::SQLiteGridViewRevisionPersistence; +use crate::services::view_editor::changed_notifier::*; +use crate::services::view_editor::trait_impl::GridViewRevisionCompress; +use crate::services::view_editor::{GridViewEditorDelegate, GridViewRevisionEditor}; +use flowy_database::ConnectionPool; +use flowy_error::FlowyResult; +use flowy_revision::{ + RevisionManager, RevisionPersistence, RevisionPersistenceConfiguration, SQLiteRevisionSnapshotPersistence, +}; +use grid_rev_model::{FilterRevision, RowChangeset, RowRevision}; +use lib_infra::future::Fut; +use lib_infra::ref_map::RefCountHashMap; +use std::sync::Arc; +use tokio::sync::{broadcast, RwLock}; + +pub struct GridViewManager { + grid_id: String, + user: Arc, + delegate: Arc, + view_editors: RwLock>>, + pub notifier: broadcast::Sender, +} + +impl GridViewManager { + pub async fn new( + grid_id: String, + user: Arc, + delegate: Arc, + ) -> FlowyResult { + let (notifier, _) = broadcast::channel(100); + tokio::spawn(GridViewChangedReceiverRunner(Some(notifier.subscribe())).run()); + let view_editors = RwLock::new(RefCountHashMap::default()); + Ok(Self { + grid_id, + user, + delegate, + view_editors, + notifier, + }) + } + + pub async fn close(&self, view_id: &str) { + self.view_editors.write().await.remove(view_id); + } + + pub async fn subscribe_view_changed(&self) -> broadcast::Receiver { + self.notifier.subscribe() + } + + pub async fn filter_rows(&self, block_id: &str, rows: Vec>) -> FlowyResult>> { + let editor = self.get_default_view_editor().await?; + let rows = editor.filter_rows(block_id, rows).await; + Ok(rows) + } + + pub async fn duplicate_grid_view(&self) -> FlowyResult { + let editor = self.get_default_view_editor().await?; + let view_data = editor.duplicate_view_data().await?; + Ok(view_data) + } + + /// When the row was created, we may need to modify the [RowRevision] according to the [CreateRowParams]. + pub async fn will_create_row(&self, row_rev: &mut RowRevision, params: &CreateRowParams) { + for view_editor in self.view_editors.read().await.values() { + view_editor.will_create_view_row(row_rev, params).await; + } + } + + /// Notify the view that the row was created. For the moment, the view is just sending notifications. + pub async fn did_create_row(&self, row_pb: &RowPB, params: &CreateRowParams) { + for view_editor in self.view_editors.read().await.values() { + view_editor.did_create_view_row(row_pb, params).await; + } + } + + /// Insert/Delete the group's row if the corresponding cell data was changed. + pub async fn did_update_cell(&self, row_id: &str) { + match self.delegate.get_row_rev(row_id).await { + None => { + tracing::warn!("Can not find the row in grid view"); + } + Some(row_rev) => { + for view_editor in self.view_editors.read().await.values() { + view_editor.did_update_view_cell(&row_rev).await; + } + } + } + } + + pub async fn group_by_field(&self, field_id: &str) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + let _ = view_editor.group_by_view_field(field_id).await?; + Ok(()) + } + + pub async fn did_delete_row(&self, row_rev: Arc) { + for view_editor in self.view_editors.read().await.values() { + view_editor.did_delete_view_row(&row_rev).await; + } + } + + pub async fn get_setting(&self) -> FlowyResult { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_view_setting().await) + } + + pub async fn get_all_filters(&self) -> FlowyResult>> { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_all_view_filters().await) + } + + pub async fn get_filters(&self, filter_id: &FilterType) -> FlowyResult>> { + let view_editor = self.get_default_view_editor().await?; + Ok(view_editor.get_view_filters(filter_id).await) + } + + pub async fn insert_or_update_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.insert_view_filter(params).await + } + + pub async fn delete_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.delete_view_filter(params).await + } + + pub async fn load_groups(&self) -> FlowyResult { + let view_editor = self.get_default_view_editor().await?; + let groups = view_editor.load_view_groups().await?; + Ok(RepeatedGridGroupPB { items: groups }) + } + + pub async fn insert_or_update_group(&self, params: InsertGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.initialize_new_group(params).await + } + + pub async fn delete_group(&self, params: DeleteGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + view_editor.delete_view_group(params).await + } + + pub async fn move_group(&self, params: MoveGroupParams) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + let _ = view_editor.move_view_group(params).await?; + Ok(()) + } + + /// It may generate a RowChangeset when the Row was moved from one group to another. + /// The return value, [RowChangeset], contains the changes made by the groups. + /// + pub async fn move_group_row( + &self, + row_rev: Arc, + to_group_id: String, + to_row_id: Option, + recv_row_changeset: impl FnOnce(RowChangeset) -> Fut<()>, + ) -> FlowyResult<()> { + let mut row_changeset = RowChangeset::new(row_rev.id.clone()); + let view_editor = self.get_default_view_editor().await?; + let group_changesets = view_editor + .move_view_group_row(&row_rev, &mut row_changeset, &to_group_id, to_row_id.clone()) + .await; + + if !row_changeset.is_empty() { + recv_row_changeset(row_changeset).await; + } + + for group_changeset in group_changesets { + view_editor.notify_did_update_group_rows(group_changeset).await; + } + + Ok(()) + } + + /// Notifies the view's field type-option data is changed + /// For the moment, only the groups will be generated after the type-option data changed. A + /// [FieldRevision] has a property named type_options contains a list of type-option data. + /// # Arguments + /// + /// * `field_id`: the id of the field in current view + /// + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn did_update_view_field_type_option(&self, field_id: &str) -> FlowyResult<()> { + let view_editor = self.get_default_view_editor().await?; + if view_editor.group_id().await == field_id { + let _ = view_editor.group_by_view_field(field_id).await?; + } + + let _ = view_editor.did_update_view_field_type_option(field_id).await?; + Ok(()) + } + + pub async fn get_view_editor(&self, view_id: &str) -> FlowyResult> { + debug_assert!(!view_id.is_empty()); + if let Some(editor) = self.view_editors.read().await.get(view_id) { + return Ok(editor); + } + tracing::trace!("{:p} create view_editor", self); + let mut view_editors = self.view_editors.write().await; + let editor = Arc::new(self.make_view_editor(view_id).await?); + view_editors.insert(view_id.to_owned(), editor.clone()); + Ok(editor) + } + + async fn get_default_view_editor(&self) -> FlowyResult> { + self.get_view_editor(&self.grid_id).await + } + + async fn make_view_editor(&self, view_id: &str) -> FlowyResult { + let rev_manager = make_grid_view_rev_manager(&self.user, view_id).await?; + let user_id = self.user.user_id()?; + let token = self.user.token()?; + let view_id = view_id.to_owned(); + + GridViewRevisionEditor::new( + &user_id, + &token, + view_id, + self.delegate.clone(), + self.notifier.clone(), + rev_manager, + ) + .await + } +} + +pub async fn make_grid_view_rev_manager( + user: &Arc, + view_id: &str, +) -> FlowyResult>> { + let user_id = user.user_id()?; + let pool = user.db_pool()?; + + let disk_cache = SQLiteGridViewRevisionPersistence::new(&user_id, pool.clone()); + let configuration = RevisionPersistenceConfiguration::new(2, false); + let rev_persistence = RevisionPersistence::new(&user_id, view_id, disk_cache, configuration); + let rev_compactor = GridViewRevisionCompress(); + + let snapshot_persistence = SQLiteRevisionSnapshotPersistence::new(view_id, pool); + Ok(RevisionManager::new( + &user_id, + view_id, + rev_persistence, + rev_compactor, + snapshot_persistence, + )) +} diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/mod.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/mod.rs new file mode 100644 index 0000000000..73e20cc18c --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/view_editor/mod.rs @@ -0,0 +1,8 @@ +mod changed_notifier; +mod editor; +mod editor_manager; +mod trait_impl; + +pub use changed_notifier::*; +pub use editor::*; +pub use editor_manager::*; diff --git a/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs b/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs new file mode 100644 index 0000000000..35b4e1df1a --- /dev/null +++ b/frontend/rust-lib/flowy-grid/src/services/view_editor/trait_impl.rs @@ -0,0 +1,166 @@ +use crate::entities::{FilterPB, GridGroupConfigurationPB, GridLayout, GridLayoutPB, GridSettingPB}; +use crate::services::filter::{FilterDelegate, FilterType}; +use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter}; +use crate::services::row::GridBlock; +use crate::services::view_editor::GridViewEditorDelegate; +use bytes::Bytes; +use flowy_database::ConnectionPool; +use flowy_error::{FlowyError, FlowyResult}; +use flowy_http_model::revision::Revision; +use flowy_revision::{ + RevisionCloudService, RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionObjectSerializer, +}; +use flowy_sync::client_grid::{GridViewRevisionChangeset, GridViewRevisionPad}; +use flowy_sync::util::make_operations_from_revisions; +use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision}; +use lib_infra::future::{to_future, Fut, FutureResult}; +use lib_ot::core::EmptyAttributes; +use std::sync::Arc; +use tokio::sync::RwLock; + +pub(crate) struct GridViewRevisionCloudService { + #[allow(dead_code)] + pub(crate) token: String, +} + +impl RevisionCloudService for GridViewRevisionCloudService { + fn fetch_object(&self, _user_id: &str, _object_id: &str) -> FutureResult, FlowyError> { + FutureResult::new(async move { Ok(vec![]) }) + } +} + +pub(crate) struct GridViewRevisionSerde(); +impl RevisionObjectDeserializer for GridViewRevisionSerde { + type Output = GridViewRevisionPad; + + fn deserialize_revisions(object_id: &str, revisions: Vec) -> FlowyResult { + let pad = GridViewRevisionPad::from_revisions(object_id, revisions)?; + Ok(pad) + } +} + +impl RevisionObjectSerializer for GridViewRevisionSerde { + fn combine_revisions(revisions: Vec) -> FlowyResult { + let operations = make_operations_from_revisions::(revisions)?; + Ok(operations.json_bytes()) + } +} + +pub(crate) struct GridViewRevisionCompress(); +impl RevisionMergeable for GridViewRevisionCompress { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + GridViewRevisionSerde::combine_revisions(revisions) + } +} + +pub(crate) struct GroupConfigurationReaderImpl(pub(crate) Arc>); + +impl GroupConfigurationReader for GroupConfigurationReaderImpl { + fn get_configuration(&self) -> Fut>> { + let view_pad = self.0.clone(); + to_future(async move { + let mut groups = view_pad.read().await.get_all_groups(); + if groups.is_empty() { + None + } else { + debug_assert_eq!(groups.len(), 1); + Some(groups.pop().unwrap()) + } + }) + } +} + +pub(crate) struct GroupConfigurationWriterImpl { + pub(crate) user_id: String, + pub(crate) rev_manager: Arc>>, + pub(crate) view_pad: Arc>, +} + +impl GroupConfigurationWriter for GroupConfigurationWriterImpl { + fn save_configuration( + &self, + field_id: &str, + field_type: FieldTypeRevision, + group_configuration: GroupConfigurationRevision, + ) -> Fut> { + let user_id = self.user_id.clone(); + let rev_manager = self.rev_manager.clone(); + let view_pad = self.view_pad.clone(); + let field_id = field_id.to_owned(); + + to_future(async move { + let changeset = view_pad.write().await.insert_or_update_group_configuration( + &field_id, + &field_type, + group_configuration, + )?; + + if let Some(changeset) = changeset { + let _ = apply_change(&user_id, rev_manager, changeset).await?; + } + Ok(()) + }) + } +} + +pub(crate) async fn apply_change( + _user_id: &str, + rev_manager: Arc>>, + change: GridViewRevisionChangeset, +) -> FlowyResult<()> { + let GridViewRevisionChangeset { operations: delta, md5 } = change; + let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); + let delta_data = delta.json_bytes(); + let revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, delta_data, md5); + let _ = rev_manager.add_local_revision(&revision).await?; + Ok(()) +} + +pub fn make_grid_setting(view_pad: &GridViewRevisionPad, field_revs: &[Arc]) -> GridSettingPB { + let layout_type: GridLayout = view_pad.layout.clone().into(); + let filter_configurations = view_pad + .get_all_filters(field_revs) + .into_iter() + .map(|filter| FilterPB::from(filter.as_ref())) + .collect::>(); + + let group_configurations = view_pad + .get_groups_by_field_revs(field_revs) + .into_iter() + .map(|group| GridGroupConfigurationPB::from(group.as_ref())) + .collect::>(); + + GridSettingPB { + layouts: GridLayoutPB::all(), + layout_type, + filter_configurations: filter_configurations.into(), + group_configurations: group_configurations.into(), + } +} + +pub(crate) struct GridViewFilterDelegateImpl { + pub(crate) editor_delegate: Arc, + pub(crate) view_revision_pad: Arc>, +} + +impl FilterDelegate for GridViewFilterDelegateImpl { + fn get_filter_rev(&self, filter_id: FilterType) -> Fut>> { + let pad = self.view_revision_pad.clone(); + to_future(async move { + let field_type_rev: FieldTypeRevision = filter_id.field_type.into(); + pad.read().await.get_filters(&filter_id.field_id, &field_type_rev) + }) + } + + fn get_field_rev(&self, field_id: &str) -> Fut>> { + self.editor_delegate.get_field_rev(field_id) + } + + fn get_field_revs(&self, field_ids: Option>) -> Fut>> { + self.editor_delegate.get_field_revs(field_ids) + } + + fn get_blocks(&self) -> Fut> { + self.editor_delegate.get_blocks() + } +} diff --git a/frontend/rust-lib/flowy-grid/src/util.rs b/frontend/rust-lib/flowy-grid/src/util.rs index 7f34774ad0..d7b8495531 100644 --- a/frontend/rust-lib/flowy-grid/src/util.rs +++ b/frontend/rust-lib/flowy-grid/src/util.rs @@ -1,8 +1,8 @@ use crate::entities::FieldType; use crate::services::field::*; use crate::services::row::RowRevisionBuilder; -use flowy_grid_data_model::revision::BuildGridContext; use flowy_sync::client_grid::GridBuilder; +use grid_rev_model::BuildGridContext; pub fn make_default_grid() -> BuildGridContext { let mut grid_builder = GridBuilder::new(); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs index 6ded1f7060..e9aa368f7d 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/block_test.rs @@ -1,7 +1,7 @@ use crate::grid::block_test::script::GridRowTest; use crate::grid::block_test::script::RowScript::*; -use flowy_grid_data_model::revision::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset}; +use grid_rev_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset}; #[tokio::test] async fn grid_create_block() { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs index 72b3a288a1..db3d735f29 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs @@ -3,7 +3,7 @@ use crate::grid::block_test::script::{CreateRowScriptBuilder, GridRowTest}; use crate::grid::grid_editor::{COMPLETED, FACEBOOK, GOOGLE, PAUSED, TWITTER}; use flowy_grid::entities::FieldType; use flowy_grid::services::field::{SELECTION_IDS_SEPARATOR, UNCHECK}; -use flowy_grid_data_model::revision::RowChangeset; +use grid_rev_model::RowChangeset; #[tokio::test] async fn grid_create_row_count_test() { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs index df0c105e39..703cf6889c 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs @@ -2,11 +2,9 @@ use crate::grid::block_test::script::RowScript::{AssertCell, CreateRow}; use crate::grid::block_test::util::GridRowTestBuilder; use crate::grid::grid_editor::GridEditorTest; -use flowy_grid::entities::{CreateRowParams, FieldType, GridCellIdParams, GridLayout, RowPB}; +use flowy_grid::entities::{CellPathParams, CreateRowParams, FieldType, GridLayout, RowPB}; use flowy_grid::services::field::*; -use flowy_grid_data_model::revision::{ - GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision, -}; +use grid_rev_model::{GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowChangeset, RowRevision}; use std::collections::HashMap; use std::sync::Arc; use strum::IntoEnumIterator; @@ -115,7 +113,7 @@ impl GridRowTest { field_type, expected, } => { - let id = GridCellIdParams { + let id = CellPathParams { grid_id: self.grid_id.clone(), field_id, row_id, @@ -160,7 +158,7 @@ impl GridRowTest { } } - async fn compare_cell_content(&self, cell_id: GridCellIdParams, field_type: FieldType, expected: String) { + async fn compare_cell_content(&self, cell_id: CellPathParams, field_type: FieldType, expected: String) { match field_type { FieldType::RichText => { let cell_data = self diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs index 03a3838afc..6179168ce1 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs @@ -2,10 +2,10 @@ use flowy_grid::entities::FieldType; use std::sync::Arc; use flowy_grid::services::field::{ - DateCellChangesetPB, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB, + DateCellChangeset, MultiSelectTypeOptionPB, SelectOptionPB, SingleSelectTypeOptionPB, }; use flowy_grid::services::row::RowRevisionBuilder; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use grid_rev_model::{FieldRevision, RowRevision}; use strum::EnumCount; @@ -38,9 +38,10 @@ impl<'a> GridRowTestBuilder<'a> { } pub fn insert_date_cell(&mut self, data: &str) -> String { - let value = serde_json::to_string(&DateCellChangesetPB { + let value = serde_json::to_string(&DateCellChangeset { date: Some(data.to_string()), time: None, + is_utc: true, }) .unwrap(); let date_field = self.field_rev_with_type(&FieldType::DateTime); diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs index 670ba6327c..ad184682bd 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/script.rs @@ -29,7 +29,7 @@ impl GridCellTest { match script { CellScript::UpdateCell { changeset, is_err } => { - let result = self.editor.update_cell(changeset).await; + let result = self.editor.update_cell_with_changeset(changeset).await; if is_err { assert!(result.is_err()) } else { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs index d0652b0d01..c187549ff8 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/script.rs @@ -1,6 +1,6 @@ use crate::grid::grid_editor::GridEditorTest; use flowy_grid::entities::{CreateFieldParams, FieldChangesetParams}; -use flowy_grid_data_model::revision::FieldRevision; +use grid_rev_model::FieldRevision; pub enum FieldScript { CreateField { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs index 5db21ecded..71de319c3a 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/test.rs @@ -4,7 +4,7 @@ use crate::grid::field_test::util::*; use flowy_grid::entities::FieldChangesetParams; use flowy_grid::services::field::selection_type_option::SelectOptionPB; use flowy_grid::services::field::SingleSelectTypeOptionPB; -use flowy_grid_data_model::revision::TypeOptionDataSerializer; +use grid_rev_model::TypeOptionDataSerializer; #[tokio::test] async fn grid_create_field() { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs index c3d2e9f88f..052962db78 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs @@ -1,7 +1,7 @@ use flowy_grid::entities::*; use flowy_grid::services::field::selection_type_option::SelectOptionPB; use flowy_grid::services::field::*; -use flowy_grid_data_model::revision::*; +use grid_rev_model::*; pub fn create_text_field(grid_id: &str) -> (CreateFieldParams, FieldRevision) { let mut field_rev = FieldBuilder::new(RichTextTypeOptionBuilder::default()) @@ -55,9 +55,10 @@ pub fn create_single_select_field(grid_id: &str) -> (CreateFieldParams, FieldRev // The grid will contains all existing field types and there are three empty rows in this grid. pub fn make_date_cell_string(s: &str) -> String { - serde_json::to_string(&DateCellChangesetPB { + serde_json::to_string(&DateCellChangeset { date: Some(s.to_string()), time: None, + is_utc: true, }) .unwrap() } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs new file mode 100644 index 0000000000..c50945a23f --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/checkbox_filter_test.rs @@ -0,0 +1,30 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::CheckboxFilterCondition; + +#[tokio::test] +async fn grid_filter_checkbox_is_check_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateCheckboxFilter { + condition: CheckboxFilterCondition::IsChecked, + }, + AssertFilterChanged { + visible_row_len: 2, + hide_row_len: 3, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_checkbox_is_uncheck_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateCheckboxFilter { + condition: CheckboxFilterCondition::IsUnChecked, + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs new file mode 100644 index 0000000000..b36aedf19f --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/date_filter_test.rs @@ -0,0 +1,78 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::DateFilterCondition; + +#[tokio::test] +async fn grid_filter_date_is_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateIs, + start: None, + end: None, + timestamp: Some(1647251762), + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_date_after_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateAfter, + start: None, + end: None, + timestamp: Some(1647251762), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_date_on_or_after_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateOnOrAfter, + start: None, + end: None, + timestamp: Some(1668359085), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_date_on_or_before_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateOnOrBefore, + start: None, + end: None, + timestamp: Some(1668359085), + }, + AssertNumberOfVisibleRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_date_within_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateDateFilter { + condition: DateFilterCondition::DateWithIn, + start: Some(1647251762), + end: Some(1668704685), + timestamp: None, + }, + AssertNumberOfVisibleRows { expected: 5 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs index 4c6980c527..c26a66ea3b 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/mod.rs @@ -1,2 +1,6 @@ +mod checkbox_filter_test; +mod date_filter_test; +mod number_filter_test; mod script; +mod select_option_filter_test; mod text_filter_test; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs new file mode 100644 index 0000000000..d39649c1af --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/number_filter_test.rs @@ -0,0 +1,82 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::NumberFilterCondition; + +#[tokio::test] +async fn grid_filter_number_is_equal_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::Equal, + content: "1".to_string(), + }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_less_than_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThan, + content: "3".to_string(), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +#[should_panic] +async fn grid_filter_number_is_less_than_test2() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThan, + content: "$3".to_string(), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_less_than_or_equal_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::LessThanOrEqualTo, + content: "3".to_string(), + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::NumberIsEmpty, + content: "".to_string(), + }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_number_is_not_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateNumberFilter { + condition: NumberFilterCondition::NumberIsNotEmpty, + content: "".to_string(), + }, + AssertNumberOfVisibleRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs index 0eff7edaa4..edd0fd351a 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs @@ -3,26 +3,70 @@ #![allow(dead_code)] #![allow(unused_imports)] -use flowy_grid::entities::{InsertFilterParams, InsertFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB}; +use std::time::Duration; +use bytes::Bytes; +use futures::TryFutureExt; +use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB}; +use flowy_grid::services::field::SelectOptionIds; use flowy_grid::services::setting::GridSettingChangesetBuilder; -use flowy_grid_data_model::revision::{FieldRevision, FieldTypeRevision}; +use grid_rev_model::{FieldRevision, FieldTypeRevision}; +use flowy_grid::services::filter::FilterType; +use flowy_grid::services::view_editor::GridViewChanged; use crate::grid::grid_editor::GridEditorTest; pub enum FilterScript { - InsertGridTableFilter { - payload: InsertFilterPayloadPB, + InsertFilter { + payload: CreateFilterPayloadPB, }, - AssertTableFilterCount { + CreateTextFilter { + condition: TextFilterCondition, + content: String, + }, + CreateNumberFilter { + condition: NumberFilterCondition, + content: String, + }, + CreateCheckboxFilter { + condition: CheckboxFilterCondition, + }, + CreateDateFilter{ + condition: DateFilterCondition, + start: Option, + end: Option, + timestamp: Option, + }, + CreateMultiSelectFilter { + condition: SelectOptionCondition, + option_ids: Vec, + }, + CreateSingleSelectFilter { + condition: SelectOptionCondition, + option_ids: Vec, + }, + AssertFilterCount { count: i32, }, - DeleteGridTableFilter { + DeleteFilter { filter_id: String, - field_rev: FieldRevision, + filter_type: FilterType, + }, + AssertFilterContent { + filter_type: FilterType, + condition: u32, + content: String + }, + AssertNumberOfVisibleRows { + expected: usize, + }, + AssertFilterChanged{ + visible_row_len:usize, + hide_row_len: usize, }, #[allow(dead_code)] AssertGridSetting { expected_setting: GridSettingPB, }, + Wait { millisecond: u64 } } pub struct GridFilterTest { @@ -45,25 +89,108 @@ impl GridFilterTest { pub async fn run_script(&mut self, script: FilterScript) { match script { - - FilterScript::InsertGridTableFilter { payload } => { - let params: InsertFilterParams = payload.try_into().unwrap(); - let _ = self.editor.create_filter(params).await.unwrap(); + FilterScript::InsertFilter { payload } => { + self.insert_filter(payload).await; } - FilterScript::AssertTableFilterCount { count } => { - let filters = self.editor.get_grid_filter().await.unwrap(); + FilterScript::CreateTextFilter { condition, content} => { + + let field_rev = self.get_field_rev(FieldType::RichText); + let text_filter= TextFilterPB { + condition, + content + }; + let payload = + CreateFilterPayloadPB::new(field_rev, text_filter); + self.insert_filter(payload).await; + } + FilterScript::CreateNumberFilter {condition, content} => { + let field_rev = self.get_field_rev(FieldType::Number); + let number_filter = NumberFilterPB { + condition, + content + }; + let payload = + CreateFilterPayloadPB::new(field_rev, number_filter); + self.insert_filter(payload).await; + } + FilterScript::CreateCheckboxFilter {condition} => { + let field_rev = self.get_field_rev(FieldType::Checkbox); + let checkbox_filter = CheckboxFilterPB { + condition + }; + let payload = + CreateFilterPayloadPB::new(field_rev, checkbox_filter); + self.insert_filter(payload).await; + } + FilterScript::CreateDateFilter { condition, start, end, timestamp} => { + let field_rev = self.get_field_rev(FieldType::DateTime); + let date_filter = DateFilterPB { + condition, + start, + end, + timestamp + }; + + let payload = + CreateFilterPayloadPB::new(field_rev, date_filter); + self.insert_filter(payload).await; + } + FilterScript::CreateMultiSelectFilter { condition, option_ids} => { + let field_rev = self.get_field_rev(FieldType::MultiSelect); + let filter = SelectOptionFilterPB { condition, option_ids }; + let payload = + CreateFilterPayloadPB::new(field_rev, filter); + self.insert_filter(payload).await; + } + FilterScript::CreateSingleSelectFilter { condition, option_ids} => { + let field_rev = self.get_field_rev(FieldType::SingleSelect); + let filter = SelectOptionFilterPB { condition, option_ids }; + let payload = + CreateFilterPayloadPB::new(field_rev, filter); + self.insert_filter(payload).await; + } + FilterScript::AssertFilterCount { count } => { + let filters = self.editor.get_all_filters().await.unwrap(); assert_eq!(count as usize, filters.len()); } - FilterScript::DeleteGridTableFilter { filter_id, field_rev} => { - let params = DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty }; + FilterScript::AssertFilterContent { filter_type: filter_id, condition, content} => { + let filter = self.editor.get_filters(filter_id).await.unwrap().pop().unwrap(); + assert_eq!(&filter.content, &content); + assert_eq!(filter.condition as u32, condition); + + } + FilterScript::DeleteFilter { filter_id, filter_type } => { + let params = DeleteFilterParams { filter_type, filter_id }; let _ = self.editor.delete_filter(params).await.unwrap(); } FilterScript::AssertGridSetting { expected_setting } => { - let setting = self.editor.get_grid_setting().await.unwrap(); + let setting = self.editor.get_setting().await.unwrap(); assert_eq!(expected_setting, setting); } + FilterScript::AssertFilterChanged { visible_row_len, hide_row_len} => { + let mut receiver = self.editor.subscribe_view_changed().await; + let changed = receiver.recv().await.unwrap(); + match changed { GridViewChanged::DidReceiveFilterResult(changed) => { + assert_eq!(changed.visible_rows.len(), visible_row_len); + assert_eq!(changed.invisible_rows.len(), hide_row_len); + } } + } + FilterScript::AssertNumberOfVisibleRows { expected } => { + // + let grid = self.editor.get_grid().await.unwrap(); + let rows = grid.blocks.into_iter().map(|block| block.rows).flatten().collect::>(); + assert_eq!(rows.len(), expected); + } + FilterScript::Wait { millisecond } => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } } } + + async fn insert_filter(&self, payload: CreateFilterPayloadPB) { + let params: CreateFilterParams = payload.try_into().unwrap(); + let _ = self.editor.create_filter(params).await.unwrap(); + } } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs new file mode 100644 index 0000000000..2a7bf18f8a --- /dev/null +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/select_option_filter_test.rs @@ -0,0 +1,84 @@ +use crate::grid::filter_test::script::FilterScript::*; +use crate::grid::filter_test::script::GridFilterTest; +use flowy_grid::entities::SelectOptionCondition; + +#[tokio::test] +async fn grid_filter_multi_select_is_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionCondition::OptionIsEmpty, + option_ids: vec![], + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_multi_select_is_not_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionCondition::OptionIsNotEmpty, + option_ids: vec![], + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_multi_select_is_test() { + let mut test = GridFilterTest::new().await; + let mut options = test.get_multi_select_type_option(); + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![options.remove(0).id, options.remove(0).id], + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_multi_select_is_test2() { + let mut test = GridFilterTest::new().await; + let mut options = test.get_multi_select_type_option(); + let scripts = vec![ + CreateMultiSelectFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![options.remove(1).id], + }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_single_select_is_empty_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateSingleSelectFilter { + condition: SelectOptionCondition::OptionIsEmpty, + option_ids: vec![], + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_single_select_is_test() { + let mut test = GridFilterTest::new().await; + let mut options = test.get_single_select_type_option(); + let scripts = vec![ + CreateSingleSelectFilter { + condition: SelectOptionCondition::OptionIs, + option_ids: vec![options.remove(0).id], + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; + test.run_scripts(scripts).await; +} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs index 5868e16c2d..5a5e2282ab 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/text_filter_test.rs @@ -1,26 +1,100 @@ use crate::grid::filter_test::script::FilterScript::*; use crate::grid::filter_test::script::*; -use flowy_grid::entities::{FieldType, InsertFilterPayloadPB, TextFilterCondition}; -use flowy_grid_data_model::revision::FieldRevision; +use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition, TextFilterPB}; +use flowy_grid::services::filter::FilterType; #[tokio::test] -async fn grid_filter_create_test() { +async fn grid_filter_text_is_empty_test() { let mut test = GridFilterTest::new().await; - let field_rev = test.get_field_rev(FieldType::RichText); - let payload = InsertFilterPayloadPB::new(field_rev, TextFilterCondition::TextIsEmpty, Some("abc".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::TextIsEmpty, + content: "".to_string(), + }, + AssertFilterCount { count: 1 }, + AssertFilterChanged { + visible_row_len: 1, + hide_row_len: 4, + }, + ]; test.run_scripts(scripts).await; } #[tokio::test] -#[should_panic] -async fn grid_filter_invalid_condition_panic_test() { +async fn grid_filter_text_is_not_empty_test() { let mut test = GridFilterTest::new().await; - let field_rev = test.get_field_rev(FieldType::RichText).clone(); + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::TextIsNotEmpty, + content: "".to_string(), + }, + AssertFilterCount { count: 1 }, + AssertFilterChanged { + visible_row_len: 4, + hide_row_len: 1, + }, + ]; + test.run_scripts(scripts).await; +} - // 100 is not a valid condition, so this test should be panic. - let payload = InsertFilterPayloadPB::new(&field_rev, 100, Some("".to_owned())); - let scripts = vec![InsertGridTableFilter { payload }]; +#[tokio::test] +async fn grid_filter_is_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::Is, + content: "A".to_string(), + }, + AssertFilterChanged { + visible_row_len: 1, + hide_row_len: 4, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_contain_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::Contains, + content: "A".to_string(), + }, + AssertFilterChanged { + visible_row_len: 3, + hide_row_len: 2, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_start_with_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::StartsWith, + content: "A".to_string(), + }, + AssertFilterChanged { + visible_row_len: 2, + hide_row_len: 3, + }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_ends_with_text_test() { + let mut test = GridFilterTest::new().await; + let scripts = vec![ + CreateTextFilter { + condition: TextFilterCondition::EndsWith, + content: "A".to_string(), + }, + AssertNumberOfVisibleRows { expected: 2 }, + ]; test.run_scripts(scripts).await; } @@ -28,24 +102,26 @@ async fn grid_filter_invalid_condition_panic_test() { async fn grid_filter_delete_test() { let mut test = GridFilterTest::new().await; let field_rev = test.get_field_rev(FieldType::RichText).clone(); - let payload = create_filter(&field_rev, TextFilterCondition::TextIsEmpty, "abc"); - let scripts = vec![InsertGridTableFilter { payload }, AssertTableFilterCount { count: 1 }]; + let text_filter = TextFilterPB { + condition: TextFilterCondition::TextIsEmpty, + content: "".to_string(), + }; + let payload = CreateFilterPayloadPB::new(&field_rev, text_filter); + let scripts = vec![ + InsertFilter { payload }, + AssertFilterCount { count: 1 }, + AssertNumberOfVisibleRows { expected: 1 }, + ]; test.run_scripts(scripts).await; let filter = test.grid_filters().await.pop().unwrap(); test.run_scripts(vec![ - DeleteGridTableFilter { + DeleteFilter { filter_id: filter.id, - field_rev: field_rev.as_ref().clone(), + filter_type: FilterType::from(&field_rev), }, - AssertTableFilterCount { count: 0 }, + AssertFilterCount { count: 0 }, + AssertNumberOfVisibleRows { expected: 5 }, ]) .await; } - -#[tokio::test] -async fn grid_filter_get_rows_test() {} - -fn create_filter(field_rev: &FieldRevision, condition: TextFilterCondition, s: &str) -> InsertFilterPayloadPB { - InsertFilterPayloadPB::new(field_rev, condition, Some(s.to_owned())) -} diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index ccccea454d..75c4552308 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -3,17 +3,18 @@ #![allow(unused_imports)] use crate::grid::block_test::util::GridRowTestBuilder; use bytes::Bytes; +use flowy_error::FlowyResult; use flowy_grid::entities::*; use flowy_grid::services::field::SelectOptionPB; use flowy_grid::services::field::*; use flowy_grid::services::grid_editor::{GridRevisionEditor, GridRevisionSerde}; use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder}; use flowy_grid::services::setting::GridSettingChangesetBuilder; -use flowy_grid_data_model::revision::*; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_sync::client_grid::GridBuilder; use flowy_test::helper::ViewTest; use flowy_test::FlowySDKTest; +use grid_rev_model::*; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; @@ -44,18 +45,23 @@ impl GridEditorTest { pub async fn new(layout: GridLayout) -> Self { let sdk = FlowySDKTest::default(); let _ = sdk.init_user().await; - let build_context = make_test_grid(); - let view_data: Bytes = build_context.into(); - let test = match layout { - GridLayout::Table => ViewTest::new_grid_view(&sdk, view_data.to_vec()).await, - GridLayout::Board => ViewTest::new_board_view(&sdk, view_data.to_vec()).await, + GridLayout::Table => { + let build_context = make_test_grid(); + let view_data: Bytes = build_context.into(); + ViewTest::new_grid_view(&sdk, view_data.to_vec()).await + } + GridLayout::Board => { + let build_context = make_test_board(); + let view_data: Bytes = build_context.into(); + ViewTest::new_board_view(&sdk, view_data.to_vec()).await + } }; let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); let field_revs = editor.get_field_revs(None).await.unwrap(); let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); - let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; + let row_revs = editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs; assert_eq!(block_meta_revs.len(), 1); // It seems like you should add the field in the make_test_grid() function. @@ -76,17 +82,11 @@ impl GridEditorTest { } pub async fn get_row_revs(&self) -> Vec> { - self.editor - .grid_block_snapshots(None) - .await - .unwrap() - .pop() - .unwrap() - .row_revs + self.editor.get_blocks(None).await.unwrap().pop().unwrap().row_revs } - pub async fn grid_filters(&self) -> Vec { - self.editor.get_grid_filter().await.unwrap() + pub async fn grid_filters(&self) -> Vec { + self.editor.get_all_filters().await.unwrap() } pub fn get_field_rev(&self, field_type: FieldType) -> &Arc { @@ -101,6 +101,23 @@ impl GridEditorTest { .unwrap() } + pub fn get_multi_select_type_option(&self) -> Vec { + let field_type = FieldType::MultiSelect; + let field_rev = self.get_field_rev(field_type.clone()); + let type_option = field_rev + .get_type_option::(field_type.into()) + .unwrap(); + type_option.options + } + + pub fn get_single_select_type_option(&self) -> Vec { + let field_type = FieldType::SingleSelect; + let field_rev = self.get_field_rev(field_type.clone()); + let type_option = field_rev + .get_type_option::(field_type.into()) + .unwrap(); + type_option.options + } pub fn block_id(&self) -> &str { &self.block_meta_revs.last().unwrap().block_id } @@ -181,6 +198,159 @@ fn make_test_grid() -> BuildGridContext { } } + // We have many assumptions base on the number of the rows, so do not change the number of the loop. + for i in 0..5 { + let block_id = grid_builder.block_id().to_owned(); + let field_revs = grid_builder.field_revs(); + let mut row_builder = GridRowTestBuilder::new(&block_id, field_revs); + match i { + 0 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("A"), + FieldType::Number => row_builder.insert_number_cell("1"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + } + 1 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell(""), + FieldType::Number => row_builder.insert_number_cell("2"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::MultiSelect => row_builder + .insert_multi_select_cell(|mut options| vec![options.remove(0), options.remove(0)]), + FieldType::Checkbox => row_builder.insert_checkbox_cell("true"), + _ => "".to_owned(), + }; + } + } + 2 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("C"), + FieldType::Number => row_builder.insert_number_cell("3"), + FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + } + FieldType::MultiSelect => { + row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)]) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + 3 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("DA"), + FieldType::Number => row_builder.insert_number_cell("4"), + FieldType::DateTime => row_builder.insert_date_cell("1668704685"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(0)) + } + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + 4 => { + for field_type in FieldType::iter() { + match field_type { + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell(""), + FieldType::DateTime => row_builder.insert_date_cell("1668359085"), + FieldType::SingleSelect => { + row_builder.insert_single_select_cell(|mut options| options.remove(1)) + } + + FieldType::Checkbox => row_builder.insert_checkbox_cell("false"), + _ => "".to_owned(), + }; + } + } + _ => {} + } + + let row_rev = row_builder.build(); + grid_builder.add_row(row_rev); + } + grid_builder.build() +} + +fn make_test_board() -> BuildGridContext { + let mut grid_builder = GridBuilder::new(); + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; + + // The + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .primary(true) + .build(); + grid_builder.add_field(text_field); + } + FieldType::Number => { + // Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); + grid_builder.add_field(number_field); + } + FieldType::DateTime => { + // Date + let date = DateTypeOptionBuilder::default() + .date_format(DateFormat::US) + .time_format(TimeFormat::TwentyFourHour); + let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); + grid_builder.add_field(date_field); + } + FieldType::SingleSelect => { + // Single Select + let single_select = SingleSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(COMPLETED)) + .add_option(SelectOptionPB::new(PLANNED)) + .add_option(SelectOptionPB::new(PAUSED)); + let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); + grid_builder.add_field(single_select_field); + } + FieldType::MultiSelect => { + // MultiSelect + let multi_select = MultiSelectTypeOptionBuilder::default() + .add_option(SelectOptionPB::new(GOOGLE)) + .add_option(SelectOptionPB::new(FACEBOOK)) + .add_option(SelectOptionPB::new(TWITTER)); + let multi_select_field = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + grid_builder.add_field(multi_select_field); + } + FieldType::Checkbox => { + // Checkbox + let checkbox = CheckboxTypeOptionBuilder::default(); + let checkbox_field = FieldBuilder::new(checkbox).name("is urgent").visibility(true).build(); + grid_builder.add_field(checkbox_field); + } + FieldType::URL => { + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + grid_builder.add_field(url_field); + } + } + } + // We have many assumptions base on the number of the rows, so do not change the number of the loop. for i in 0..5 { let block_id = grid_builder.block_id().to_owned(); @@ -239,9 +409,9 @@ fn make_test_grid() -> BuildGridContext { 3 => { for field_type in FieldType::iter() { match field_type { - FieldType::RichText => row_builder.insert_text_cell("D"), + FieldType::RichText => row_builder.insert_text_cell("DA"), FieldType::Number => row_builder.insert_number_cell("4"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::DateTime => row_builder.insert_date_cell("1668704685"), FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(1)) } @@ -253,9 +423,9 @@ fn make_test_grid() -> BuildGridContext { 4 => { for field_type in FieldType::iter() { match field_type { - FieldType::RichText => row_builder.insert_text_cell("E"), - FieldType::Number => row_builder.insert_number_cell("5"), - FieldType::DateTime => row_builder.insert_date_cell("1647251762"), + FieldType::RichText => row_builder.insert_text_cell("AE"), + FieldType::Number => row_builder.insert_number_cell(""), + FieldType::DateTime => row_builder.insert_date_cell("1668359085"), FieldType::SingleSelect => { row_builder.insert_single_select_cell(|mut options| options.remove(2)) } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs index 42e4e860e0..61e3b8385d 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs @@ -6,7 +6,7 @@ use flowy_grid::services::field::{ SingleSelectTypeOption, SELECTION_IDS_SEPARATOR, }; use flowy_grid::services::row::{decode_cell_data_from_type_option_cell_data, CreateRowMetaBuilder}; -use flowy_grid_data_model::entities::{ +use grid_rev_model::entities::{ CellChangeset, FieldChangesetParams, FieldType, GridBlockInfoChangeset, GridBlockMetaSnapshot, RowMetaChangeset, TypeOptionDataFormat, }; diff --git a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs index 2c3239370d..3e673889a4 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs @@ -6,7 +6,7 @@ use flowy_grid::services::cell::{delete_select_option_cell, insert_select_option use flowy_grid::services::field::{ edit_single_select_type_option, SelectOptionPB, SelectTypeOptionSharedAction, SingleSelectTypeOptionPB, }; -use flowy_grid_data_model::revision::{FieldRevision, RowChangeset}; +use grid_rev_model::{FieldRevision, RowChangeset}; use std::sync::Arc; pub enum GroupScript { diff --git a/frontend/rust-lib/flowy-grid/tests/grid/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/script.rs index eb65e2a7c8..f8bf90dac2 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/script.rs @@ -2,15 +2,15 @@ use bytes::Bytes; use flowy_grid::services::field::*; use flowy_grid::services::grid_meta_editor::{GridMetaEditor, GridPadBuilder}; use flowy_grid::services::row::CreateRowMetaPayload; -use flowy_grid_data_model::entities::{ - BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, - GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder, - TypeOptionDataFormat, -}; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; use flowy_sync::client_grid::GridBuilder; use flowy_test::helper::ViewTest; use flowy_test::FlowySDKTest; +use grid_rev_model::entities::{ + BuildGridContext, CellChangeset, Field, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, + GridBlockInfoChangeset, GridBlockMetaSnapshot, InsertFieldParams, RowMeta, RowMetaChangeset, RowOrder, + TypeOptionDataFormat, +}; use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; diff --git a/frontend/rust-lib/flowy-net/Cargo.toml b/frontend/rust-lib/flowy-net/Cargo.toml index fee2b433b6..459dfff32b 100644 --- a/frontend/rust-lib/flowy-net/Cargo.toml +++ b/frontend/rust-lib/flowy-net/Cargo.toml @@ -10,7 +10,8 @@ lib-dispatch = { path = "../lib-dispatch" } flowy-error = { path = "../flowy-error", features = ["collaboration", "http_server"] } flowy-derive = { path = "../../../shared-lib/flowy-derive" } flowy-sync = { path = "../../../shared-lib/flowy-sync"} -flowy-folder-data-model = { path = "../../../shared-lib/flowy-folder-data-model"} +flowy-http-model = { path = "../../../shared-lib/flowy-http-model"} +folder-rev-model = { path = "../../../shared-lib/folder-rev-model"} flowy-folder = { path = "../flowy-folder" } flowy-user = { path = "../flowy-user" } flowy-document = { path = "../flowy-document" } @@ -21,7 +22,7 @@ lib-ws = { path = "../../../shared-lib/lib-ws" } bytes = { version = "1.0" } anyhow = "1.0" tokio = {version = "1", features = ["sync"]} -parking_lot = "0.11" +parking_lot = "0.12.1" strum = "0.21" strum_macros = "0.21" tracing = { version = "0.1", features = ["log"] } @@ -43,10 +44,8 @@ http_server = [] dart = [ "lib-infra/dart", "flowy-user/dart", - "flowy-sync/dart", "flowy-error/dart", - "flowy-folder-data-model/dart" ] [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen"] } \ No newline at end of file +lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] } \ No newline at end of file diff --git a/frontend/rust-lib/flowy-net/src/entities/network_state.rs b/frontend/rust-lib/flowy-net/src/entities/network_state.rs index 15a9282c88..162c0bc05e 100644 --- a/frontend/rust-lib/flowy-net/src/entities/network_state.rs +++ b/frontend/rust-lib/flowy-net/src/entities/network_state.rs @@ -6,15 +6,15 @@ pub enum NetworkType { Wifi = 1, Cell = 2, Ethernet = 3, + Bluetooth = 4, + VPN = 5, } impl NetworkType { pub fn is_connect(&self) -> bool { match self { - NetworkType::UnknownNetworkType => false, - NetworkType::Wifi => true, - NetworkType::Cell => true, - NetworkType::Ethernet => true, + NetworkType::UnknownNetworkType | NetworkType::Bluetooth => false, + NetworkType::Wifi | NetworkType::Cell | NetworkType::Ethernet | NetworkType::VPN => true, } } } diff --git a/frontend/rust-lib/flowy-net/src/http_server/document.rs b/frontend/rust-lib/flowy-net/src/http_server/document.rs index 5139d6e146..d372145cb3 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/document.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/document.rs @@ -4,7 +4,7 @@ use crate::{ }; use flowy_document::DocumentCloudService; use flowy_error::FlowyError; -use flowy_sync::entities::document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}; +use flowy_http_model::document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}; use http_flowy::response::FlowyResponse; use lazy_static::lazy_static; use lib_infra::future::FutureResult; diff --git a/frontend/rust-lib/flowy-net/src/http_server/folder.rs b/frontend/rust-lib/flowy-net/src/http_server/folder.rs index e600d617a9..1fb9d64d7b 100644 --- a/frontend/rust-lib/flowy-net/src/http_server/folder.rs +++ b/frontend/rust-lib/flowy-net/src/http_server/folder.rs @@ -10,7 +10,7 @@ use flowy_folder::entities::{ {AppIdPB, CreateAppParams, UpdateAppParams}, }; use flowy_folder::event_map::FolderCouldServiceV1; -use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use folder_rev_model::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use http_flowy::errors::ServerError; use http_flowy::response::FlowyResponse; use lazy_static::lazy_static; diff --git a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs index 9e15c0d246..b4faa8d5b5 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/persistence.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/persistence.rs @@ -1,6 +1,7 @@ -use flowy_sync::entities::revision::{RepeatedRevision, Revision}; +use flowy_http_model::document::DocumentPayloadPB; +use flowy_http_model::folder::FolderInfo; +use flowy_http_model::revision::{RepeatedRevision, Revision}; use flowy_sync::{ - entities::{document::DocumentPayloadPB, folder::FolderInfo}, errors::CollaborateError, server_document::*, server_folder::FolderCloudPersistence, diff --git a/frontend/rust-lib/flowy-net/src/local_server/server.rs b/frontend/rust-lib/flowy-net/src/local_server/server.rs index b0acecb839..b687452bf7 100644 --- a/frontend/rust-lib/flowy-net/src/local_server/server.rs +++ b/frontend/rust-lib/flowy-net/src/local_server/server.rs @@ -4,13 +4,7 @@ use bytes::Bytes; use flowy_error::{internal_error, FlowyError}; use flowy_folder::event_map::FolderCouldServiceV1; use flowy_sync::{ - client_document::default::initial_document_str, - entities::{ - document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}, - ws_data::{ClientRevisionWSData, ClientRevisionWSDataType}, - }, errors::CollaborateError, - protobuf::ClientRevisionWSData as ClientRevisionWSDataPB, server_document::ServerDocumentManager, server_folder::ServerFolderManager, synchronizer::{RevisionSyncResponse, RevisionUser}, @@ -259,13 +253,14 @@ use flowy_folder::entities::{ view::{CreateViewParams, RepeatedViewIdPB, UpdateViewParams, ViewIdPB}, workspace::{CreateWorkspaceParams, UpdateWorkspaceParams, WorkspaceIdPB}, }; -use flowy_folder_data_model::revision::{ - gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision, -}; +use flowy_http_model::document::{CreateDocumentParams, DocumentIdPB, DocumentPayloadPB, ResetDocumentParams}; +use flowy_http_model::protobuf::ClientRevisionWSData as ClientRevisionWSDataPB; +use flowy_http_model::ws_data::{ClientRevisionWSData, ClientRevisionWSDataType}; use flowy_user::entities::{ SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, }; use flowy_user::event_map::UserCloudService; +use folder_rev_model::{gen_app_id, gen_workspace_id, AppRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use lib_infra::{future::FutureResult, util::timestamp}; impl FolderCouldServiceV1 for LocalServer { @@ -308,7 +303,7 @@ impl FolderCouldServiceV1 for LocalServer { app_id: params.belong_to_id, name: params.name, desc: params.desc, - data_type: params.data_type.into(), + data_format: params.data_format.into(), version: 0, belongings: vec![], modified_time: time, @@ -422,15 +417,9 @@ impl DocumentCloudService for LocalServer { fn fetch_document( &self, _token: &str, - params: DocumentIdPB, + _params: DocumentIdPB, ) -> FutureResult, FlowyError> { - let doc = DocumentPayloadPB { - doc_id: params.value, - content: initial_document_str(), - rev_id: 0, - base_rev_id: 0, - }; - FutureResult::new(async { Ok(Some(doc)) }) + FutureResult::new(async { Ok(None) }) } fn update_document_content(&self, _token: &str, _params: ResetDocumentParams) -> FutureResult<(), FlowyError> { diff --git a/frontend/rust-lib/flowy-revision/Cargo.toml b/frontend/rust-lib/flowy-revision/Cargo.toml index 110c606879..6e5128ae04 100644 --- a/frontend/rust-lib/flowy-revision/Cargo.toml +++ b/frontend/rust-lib/flowy-revision/Cargo.toml @@ -6,13 +6,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -flowy-sync = { path = "../../../shared-lib/flowy-sync" } +flowy-http-model = { path = "../../../shared-lib/flowy-http-model" } lib-ws = { path = "../../../shared-lib/lib-ws" } lib-infra = { path = "../../../shared-lib/lib-infra" } -flowy-database = { path = "../flowy-database" } -flowy-error = { path = "../flowy-error", features = ["collaboration", "ot", "http_server", "serde", "db"] } -diesel = {version = "1.4.8", features = ["sqlite"]} -diesel_derives = {version = "1.4.1", features = ["sqlite"]} +flowy-error = { path = "../flowy-error" } tracing = { version = "0.1", features = ["log"] } tokio = {version = "1", features = ["sync"]} bytes = { version = "1.1" } @@ -24,5 +21,12 @@ futures-util = "0.3.15" async-stream = "0.3.2" serde_json = {version = "1.0"} +[dev-dependencies] +nanoid = "0.4.0" +flowy-revision = {path = "../flowy-revision", features = ["flowy_unit_test"]} +serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0" } +parking_lot = "0.12.1" + [features] flowy_unit_test = [] \ No newline at end of file diff --git a/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs b/frontend/rust-lib/flowy-revision/src/cache/disk.rs similarity index 71% rename from frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs rename to frontend/rust-lib/flowy-revision/src/cache/disk.rs index 501d1e591b..b2982cda2a 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/disk/mod.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/disk.rs @@ -1,35 +1,24 @@ -mod document_impl; -mod grid_block_impl; -mod grid_impl; -mod grid_view_impl; - -pub use document_impl::*; -pub use grid_block_impl::*; -pub use grid_impl::*; -pub use grid_view_impl::*; - use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::revision::{RevId, Revision, RevisionRange}; +use flowy_http_model::revision::{RevId, Revision, RevisionRange}; use std::fmt::Debug; use std::sync::Arc; -pub trait RevisionDiskCache: Sync + Send { +pub trait RevisionDiskCache: Sync + Send { type Error: Debug; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error>; + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error>; + + fn get_connection(&self) -> Result; // Read all the records if the rev_ids is None - fn read_revision_records( - &self, - object_id: &str, - rev_ids: Option>, - ) -> Result, Self::Error>; + fn read_revision_records(&self, object_id: &str, rev_ids: Option>) + -> Result, Self::Error>; // Read the revision which rev_id >= range.start && rev_id <= range.end fn read_revision_records_with_range( &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error>; + ) -> Result, Self::Error>; fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()>; @@ -42,25 +31,29 @@ pub trait RevisionDiskCache: Sync + Send { &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error>; } -impl RevisionDiskCache for Arc +impl RevisionDiskCache for Arc where - T: RevisionDiskCache, + T: RevisionDiskCache, { type Error = FlowyError; - fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { (**self).create_revision_records(revision_records) } + fn get_connection(&self) -> Result { + (**self).get_connection() + } + fn read_revision_records( &self, object_id: &str, rev_ids: Option>, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { (**self).read_revision_records(object_id, rev_ids) } @@ -68,7 +61,7 @@ where &self, object_id: &str, range: &RevisionRange, - ) -> Result, Self::Error> { + ) -> Result, Self::Error> { (**self).read_revision_records_with_range(object_id, range) } @@ -84,20 +77,20 @@ where &self, object_id: &str, deleted_rev_ids: Option>, - inserted_records: Vec, + inserted_records: Vec, ) -> Result<(), Self::Error> { (**self).delete_and_insert_records(object_id, deleted_rev_ids, inserted_records) } } #[derive(Clone, Debug)] -pub struct RevisionRecord { +pub struct SyncRecord { pub revision: Revision, pub state: RevisionState, pub write_to_disk: bool, } -impl RevisionRecord { +impl SyncRecord { pub fn new(revision: Revision) -> Self { Self { revision, @@ -112,9 +105,9 @@ impl RevisionRecord { } pub struct RevisionChangeset { - pub(crate) object_id: String, - pub(crate) rev_id: RevId, - pub(crate) state: RevisionState, + pub object_id: String, + pub rev_id: RevId, + pub state: RevisionState, } /// Sync: revision is not synced to the server diff --git a/frontend/rust-lib/flowy-revision/src/cache/memory.rs b/frontend/rust-lib/flowy-revision/src/cache/memory.rs index 6120c3f224..8b222eab3f 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/memory.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/memory.rs @@ -1,21 +1,21 @@ -use crate::disk::RevisionRecord; +use crate::disk::SyncRecord; use crate::REVISION_WRITE_INTERVAL_IN_MILLIS; use dashmap::DashMap; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::revision::RevisionRange; +use flowy_http_model::revision::RevisionRange; use std::{borrow::Cow, sync::Arc, time::Duration}; use tokio::{sync::RwLock, task::JoinHandle}; pub(crate) trait RevisionMemoryCacheDelegate: Send + Sync { - fn checkpoint_tick(&self, records: Vec) -> FlowyResult<()>; + fn send_sync(&self, records: Vec) -> FlowyResult<()>; fn receive_ack(&self, object_id: &str, rev_id: i64); } pub(crate) struct RevisionMemoryCache { object_id: String, - revs_map: Arc>, + revs_map: Arc>, delegate: Arc, - pending_write_revs: Arc>>, + defer_write_revs: Arc>>, defer_save: RwLock>>, } @@ -25,7 +25,7 @@ impl RevisionMemoryCache { object_id: object_id.to_owned(), revs_map: Arc::new(DashMap::new()), delegate, - pending_write_revs: Arc::new(RwLock::new(vec![])), + defer_write_revs: Arc::new(RwLock::new(vec![])), defer_save: RwLock::new(None), } } @@ -34,7 +34,7 @@ impl RevisionMemoryCache { self.revs_map.contains_key(rev_id) } - pub(crate) async fn add<'a>(&'a self, record: Cow<'a, RevisionRecord>) { + pub(crate) async fn add<'a>(&'a self, record: Cow<'a, SyncRecord>) { let record = match record { Cow::Borrowed(record) => record.clone(), Cow::Owned(record) => record, @@ -43,11 +43,11 @@ impl RevisionMemoryCache { let rev_id = record.revision.rev_id; self.revs_map.insert(rev_id, record); - let mut write_guard = self.pending_write_revs.write().await; + let mut write_guard = self.defer_write_revs.write().await; if !write_guard.contains(&rev_id) { write_guard.push(rev_id); drop(write_guard); - self.make_checkpoint().await; + self.tick_checkpoint().await; } } @@ -57,8 +57,8 @@ impl RevisionMemoryCache { Some(mut record) => record.ack(), } - if self.pending_write_revs.read().await.contains(rev_id) { - self.make_checkpoint().await; + if self.defer_write_revs.read().await.contains(rev_id) { + self.tick_checkpoint().await; } else { // The revision must be saved on disk if the pending_write_revs // doesn't contains the rev_id. @@ -66,7 +66,7 @@ impl RevisionMemoryCache { } } - pub(crate) async fn get(&self, rev_id: &i64) -> Option { + pub(crate) async fn get(&self, rev_id: &i64) -> Option { self.revs_map.get(rev_id).map(|r| r.value().clone()) } @@ -80,21 +80,25 @@ impl RevisionMemoryCache { } } - pub(crate) async fn get_with_range(&self, range: &RevisionRange) -> Result, FlowyError> { + pub(crate) async fn get_with_range(&self, range: &RevisionRange) -> Result, FlowyError> { let revs = range .iter() .flat_map(|rev_id| self.revs_map.get(&rev_id).map(|record| record.clone())) - .collect::>(); + .collect::>(); Ok(revs) } - pub(crate) async fn reset_with_revisions(&self, revision_records: Vec) { + pub(crate) fn number_of_sync_records(&self) -> usize { + self.revs_map.len() + } + + pub(crate) async fn reset_with_revisions(&self, revision_records: Vec) { self.revs_map.clear(); if let Some(handler) = self.defer_save.write().await.take() { handler.abort(); } - let mut write_guard = self.pending_write_revs.write().await; + let mut write_guard = self.defer_write_revs.write().await; write_guard.clear(); for record in revision_records { write_guard.push(record.revision.rev_id); @@ -102,21 +106,21 @@ impl RevisionMemoryCache { } drop(write_guard); - self.make_checkpoint().await; + self.tick_checkpoint().await; } - async fn make_checkpoint(&self) { + async fn tick_checkpoint(&self) { // https://github.com/async-graphql/async-graphql/blob/ed8449beec3d9c54b94da39bab33cec809903953/src/dataloader/mod.rs#L362 if let Some(handler) = self.defer_save.write().await.take() { handler.abort(); } - if self.pending_write_revs.read().await.is_empty() { + if self.defer_write_revs.read().await.is_empty() { return; } let rev_map = self.revs_map.clone(); - let pending_write_revs = self.pending_write_revs.clone(); + let pending_write_revs = self.defer_write_revs.clone(); let delegate = self.delegate.clone(); *self.defer_save.write().await = Some(tokio::spawn(async move { @@ -128,7 +132,7 @@ impl RevisionMemoryCache { // // Use saturating_sub and split_off ? // https://stackoverflow.com/questions/28952411/what-is-the-idiomatic-way-to-pop-the-last-n-elements-in-a-mutable-vec - let mut save_records: Vec = vec![]; + let mut save_records: Vec = vec![]; revs_write_guard.iter().for_each(|rev_id| match rev_map.get(rev_id) { None => {} Some(value) => { @@ -136,7 +140,7 @@ impl RevisionMemoryCache { } }); - if delegate.checkpoint_tick(save_records).is_ok() { + if delegate.send_sync(save_records).is_ok() { revs_write_guard.clear(); drop(revs_write_guard); } diff --git a/frontend/rust-lib/flowy-revision/src/cache/reset.rs b/frontend/rust-lib/flowy-revision/src/cache/reset.rs index 6f5a760a95..534302b630 100644 --- a/frontend/rust-lib/flowy-revision/src/cache/reset.rs +++ b/frontend/rust-lib/flowy-revision/src/cache/reset.rs @@ -1,9 +1,8 @@ -use crate::disk::{RevisionDiskCache, RevisionRecord}; -use crate::{RevisionLoader, RevisionPersistence}; +use crate::disk::{RevisionDiskCache, SyncRecord}; +use crate::{RevisionLoader, RevisionPersistence, RevisionPersistenceConfiguration}; use bytes::Bytes; -use flowy_database::kv::KV; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::revision::Revision; +use flowy_http_model::revision::Revision; use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::sync::Arc; @@ -16,19 +15,24 @@ pub trait RevisionResettable { // String in json format fn default_target_rev_str(&self) -> FlowyResult; + + fn read_record(&self) -> Option; + + fn set_record(&self, record: String); } -pub struct RevisionStructReset { +pub struct RevisionStructReset { user_id: String, target: T, - disk_cache: Arc>, + disk_cache: Arc>, } -impl RevisionStructReset +impl RevisionStructReset where T: RevisionResettable, + C: 'static, { - pub fn new(user_id: &str, object: T, disk_cache: Arc>) -> Self { + pub fn new(user_id: &str, object: T, disk_cache: Arc>) -> Self { Self { user_id: user_id.to_owned(), target: object, @@ -37,18 +41,18 @@ where } pub async fn run(&self) -> FlowyResult<()> { - match KV::get_str(self.target.target_id()) { + match self.target.read_record() { None => { let _ = self.reset_object().await?; let _ = self.save_migrate_record()?; } Some(s) => { - let mut record = MigrationGridRecord::from_str(&s)?; + let mut record = MigrationObjectRecord::from_str(&s).map_err(|e| FlowyError::serde().context(e))?; let rev_str = self.target.default_target_rev_str()?; if record.len < rev_str.len() { let _ = self.reset_object().await?; record.len = rev_str.len(); - KV::set_str(self.target.target_id(), record.to_string()); + self.target.set_record(record.to_string()); } } } @@ -56,10 +60,12 @@ where } async fn reset_object(&self) -> FlowyResult<()> { + let configuration = RevisionPersistenceConfiguration::new(2, false); let rev_persistence = Arc::new(RevisionPersistence::from_disk_cache( &self.user_id, self.target.target_id(), self.disk_cache.clone(), + configuration, )); let (revisions, _) = RevisionLoader { object_id: self.target.target_id().to_owned(), @@ -71,8 +77,8 @@ where .await?; let bytes = self.target.reset_data(revisions)?; - let revision = Revision::initial_revision(&self.user_id, self.target.target_id(), bytes); - let record = RevisionRecord::new(revision); + let revision = Revision::initial_revision(self.target.target_id(), bytes); + let record = SyncRecord::new(revision); tracing::trace!("Reset {} revision record object", self.target.target_id()); let _ = self @@ -84,30 +90,30 @@ where fn save_migrate_record(&self) -> FlowyResult<()> { let rev_str = self.target.default_target_rev_str()?; - let record = MigrationGridRecord { + let record = MigrationObjectRecord { object_id: self.target.target_id().to_owned(), len: rev_str.len(), }; - KV::set_str(self.target.target_id(), record.to_string()); + self.target.set_record(record.to_string()); Ok(()) } } #[derive(Serialize, Deserialize)] -struct MigrationGridRecord { +struct MigrationObjectRecord { object_id: String, len: usize, } -impl FromStr for MigrationGridRecord { +impl FromStr for MigrationObjectRecord { type Err = serde_json::Error; fn from_str(s: &str) -> Result { - serde_json::from_str::(s) + serde_json::from_str::(s) } } -impl ToString for MigrationGridRecord { +impl ToString for MigrationObjectRecord { fn to_string(&self) -> String { serde_json::to_string(self).unwrap_or_else(|_| "".to_string()) } diff --git a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs b/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs index fa5b79e3a1..794059b5f2 100644 --- a/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs +++ b/frontend/rust-lib/flowy-revision/src/conflict_resolve.rs @@ -1,14 +1,12 @@ -use crate::RevisionManager; +use crate::{RevisionMD5, RevisionManager}; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::{ +use flowy_http_model::{ revision::{RepeatedRevision, Revision, RevisionRange}, ws_data::ServerRevisionWSDataType, }; use lib_infra::future::BoxResultFuture; - use std::{convert::TryFrom, sync::Arc}; -pub type OperationsMD5 = String; pub struct TransformOperations { pub client_operations: Operations, @@ -28,12 +26,12 @@ pub trait ConflictResolver where Operations: Send + Sync, { - fn compose_operations(&self, operations: Operations) -> BoxResultFuture; + fn compose_operations(&self, operations: Operations) -> BoxResultFuture; fn transform_operations( &self, operations: Operations, ) -> BoxResultFuture, FlowyError>; - fn reset_operations(&self, operations: Operations) -> BoxResultFuture; + fn reset_operations(&self, operations: Operations) -> BoxResultFuture; } pub trait ConflictRevisionSink: Send + Sync + 'static { @@ -41,25 +39,26 @@ pub trait ConflictRevisionSink: Send + Sync + 'static { fn ack(&self, rev_id: String, ty: ServerRevisionWSDataType) -> BoxResultFuture<(), FlowyError>; } -pub struct ConflictController +pub struct ConflictController where Operations: Send + Sync, { user_id: String, resolver: Arc + Send + Sync>, rev_sink: Arc, - rev_manager: Arc, + rev_manager: Arc>, } -impl ConflictController +impl ConflictController where Operations: Clone + Send + Sync, + Connection: 'static, { pub fn new( user_id: &str, resolver: Arc + Send + Sync>, rev_sink: Arc, - rev_manager: Arc, + rev_manager: Arc>, ) -> Self { let user_id = user_id.to_owned(); Self { @@ -71,9 +70,10 @@ where } } -impl ConflictController +impl ConflictController where Operations: OperationsSerializer + OperationsDeserializer + Clone + Send + Sync, + Connection: Send + Sync + 'static, { pub async fn receive_bytes(&self, bytes: Bytes) -> FlowyResult<()> { let repeated_revision = RepeatedRevision::try_from(bytes)?; @@ -127,9 +127,8 @@ where // The server_prime is None means the client local revisions conflict with the // // server, and it needs to override the client delta. let md5 = self.resolver.reset_operations(client_operations).await?; - let repeated_revision = RepeatedRevision::new(revisions); - assert_eq!(repeated_revision.last().unwrap().md5, md5); - let _ = self.rev_manager.reset_object(repeated_revision).await?; + debug_assert!(md5.is_equal(&revisions.last().unwrap().md5)); + let _ = self.rev_manager.reset_object(revisions).await?; Ok(None) } Some(server_operations) => { @@ -151,25 +150,26 @@ where } } -fn make_client_and_server_revision( - user_id: &str, - rev_manager: &Arc, +fn make_client_and_server_revision( + _user_id: &str, + rev_manager: &Arc>, client_operations: Operations, server_operations: Option, - md5: String, + md5: RevisionMD5, ) -> (Revision, Option) where Operations: OperationsSerializer, + Connection: 'static, { let (base_rev_id, rev_id) = rev_manager.next_rev_id_pair(); let bytes = client_operations.serialize_operations(); - let client_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, user_id, md5.clone()); + let client_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5.clone()); match server_operations { None => (client_revision, None), Some(operations) => { let bytes = operations.serialize_operations(); - let server_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, user_id, md5); + let server_revision = Revision::new(&rev_manager.object_id, base_rev_id, rev_id, bytes, md5); (client_revision, Some(server_revision)) } } diff --git a/frontend/rust-lib/flowy-revision/src/history/persistence.rs b/frontend/rust-lib/flowy-revision/src/history/persistence.rs index 9c1bdacc9e..e422b08ab6 100644 --- a/frontend/rust-lib/flowy-revision/src/history/persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/history/persistence.rs @@ -5,7 +5,7 @@ use flowy_database::{ ConnectionPool, }; use flowy_error::{internal_error, FlowyResult}; -use flowy_sync::entities::revision::Revision; +use flowy_http_model::revision::Revision; use std::sync::Arc; pub struct SQLiteRevisionHistoryPersistence { diff --git a/frontend/rust-lib/flowy-revision/src/history/rev_history.rs b/frontend/rust-lib/flowy-revision/src/history/rev_history.rs index 71bdc0c333..b7802d248d 100644 --- a/frontend/rust-lib/flowy-revision/src/history/rev_history.rs +++ b/frontend/rust-lib/flowy-revision/src/history/rev_history.rs @@ -2,7 +2,7 @@ use crate::{RevisionCompactor, RevisionHistory}; use async_stream::stream; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::revision::Revision; +use flowy_http_model::revision::Revision; use futures_util::future::BoxFuture; use futures_util::stream::StreamExt; use futures_util::FutureExt; diff --git a/frontend/rust-lib/flowy-revision/src/lib.rs b/frontend/rust-lib/flowy-revision/src/lib.rs index b7fd8a12e6..83656c87f7 100644 --- a/frontend/rust-lib/flowy-revision/src/lib.rs +++ b/frontend/rust-lib/flowy-revision/src/lib.rs @@ -13,6 +13,3 @@ pub use rev_manager::*; pub use rev_persistence::*; pub use snapshot::*; pub use ws_manager::*; - -#[macro_use] -extern crate flowy_database; diff --git a/frontend/rust-lib/flowy-revision/src/rev_manager.rs b/frontend/rust-lib/flowy-revision/src/rev_manager.rs index e7384b1190..4bffdea872 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_manager.rs @@ -2,11 +2,11 @@ use crate::disk::RevisionState; use crate::{RevisionPersistence, RevisionSnapshotDiskCache, RevisionSnapshotManager, WSDataProviderDataSource}; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::{ - entities::revision::{RepeatedRevision, Revision, RevisionRange}, - util::{pair_rev_id_from_revisions, RevIdCounter}, -}; +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; use lib_infra::future::FutureResult; +use std::sync::atomic::AtomicI64; +use std::sync::atomic::Ordering::SeqCst; use std::sync::Arc; pub trait RevisionCloudService: Send + Sync { @@ -33,22 +33,17 @@ pub trait RevisionObjectDeserializer: Send + Sync { } pub trait RevisionObjectSerializer: Send + Sync { - /// Serialize the list of revisions to `Bytes` + /// Serialize a list of revisions into one in `Bytes` format /// /// * `revisions`: a list of revisions will be serialized to `Bytes` /// - fn serialize_revisions(revisions: Vec) -> FlowyResult; + fn combine_revisions(revisions: Vec) -> FlowyResult; } /// `RevisionCompress` is used to compress multiple revisions into one revision /// -pub trait RevisionCompress: Send + Sync { - fn compress_revisions( - &self, - user_id: &str, - object_id: &str, - mut revisions: Vec, - ) -> FlowyResult { +pub trait RevisionMergeable: Send + Sync { + fn merge_revisions(&self, _user_id: &str, object_id: &str, mut revisions: Vec) -> FlowyResult { if revisions.is_empty() { return Err(FlowyError::internal().context("Can't compact the empty revisions")); } @@ -62,43 +57,41 @@ pub trait RevisionCompress: Send + Sync { let (base_rev_id, rev_id) = first_revision.pair_rev_id(); let md5 = last_revision.md5.clone(); - let bytes = self.serialize_revisions(revisions)?; - Ok(Revision::new(object_id, base_rev_id, rev_id, bytes, user_id, md5)) + let bytes = self.combine_revisions(revisions)?; + Ok(Revision::new(object_id, base_rev_id, rev_id, bytes, md5)) } - fn serialize_revisions(&self, revisions: Vec) -> FlowyResult; + fn combine_revisions(&self, revisions: Vec) -> FlowyResult; } -pub struct RevisionManager { +pub struct RevisionManager { pub object_id: String, user_id: String, rev_id_counter: RevIdCounter, - rev_persistence: Arc, + rev_persistence: Arc>, #[allow(dead_code)] rev_snapshot: Arc, - rev_compress: Arc, + rev_compress: Arc, #[cfg(feature = "flowy_unit_test")] rev_ack_notifier: tokio::sync::broadcast::Sender, } -impl RevisionManager { +impl RevisionManager { pub fn new( user_id: &str, object_id: &str, - rev_persistence: RevisionPersistence, - rev_compactor: C, + rev_persistence: RevisionPersistence, + rev_compress: C, snapshot_persistence: SP, ) -> Self where SP: 'static + RevisionSnapshotDiskCache, - C: 'static + RevisionCompress, + C: 'static + RevisionMergeable, { let rev_id_counter = RevIdCounter::new(0); - let rev_compactor = Arc::new(rev_compactor); + let rev_compress = Arc::new(rev_compress); let rev_persistence = Arc::new(rev_persistence); let rev_snapshot = Arc::new(RevisionSnapshotManager::new(user_id, object_id, snapshot_persistence)); - #[cfg(feature = "flowy_unit_test")] - let (revision_ack_notifier, _) = tokio::sync::broadcast::channel(1); Self { object_id: object_id.to_string(), @@ -106,14 +99,14 @@ impl RevisionManager { rev_id_counter, rev_persistence, rev_snapshot, - rev_compress: rev_compactor, + rev_compress, #[cfg(feature = "flowy_unit_test")] - rev_ack_notifier: revision_ack_notifier, + rev_ack_notifier: tokio::sync::broadcast::channel(1).0, } } #[tracing::instrument(level = "debug", skip_all, fields(object_id) err)] - pub async fn load(&mut self, cloud: Option>) -> FlowyResult + pub async fn initialize(&mut self, cloud: Option>) -> FlowyResult where B: RevisionObjectDeserializer, { @@ -130,10 +123,26 @@ impl RevisionManager { B::deserialize_revisions(&self.object_id, revisions) } + pub async fn close(&self) { + let _ = self.rev_persistence.compact_lagging_revisions(&self.rev_compress).await; + } + + pub async fn load_revisions(&self) -> FlowyResult> { + let revisions = RevisionLoader { + object_id: self.object_id.clone(), + user_id: self.user_id.clone(), + cloud: None, + rev_persistence: self.rev_persistence.clone(), + } + .load_revisions() + .await?; + Ok(revisions) + } + #[tracing::instrument(level = "debug", skip(self, revisions), err)] - pub async fn reset_object(&self, revisions: RepeatedRevision) -> FlowyResult<()> { + pub async fn reset_object(&self, revisions: Vec) -> FlowyResult<()> { let rev_id = pair_rev_id_from_revisions(&revisions).1; - let _ = self.rev_persistence.reset(revisions.into_inner()).await?; + let _ = self.rev_persistence.reset(revisions).await?; self.rev_id_counter.set(rev_id); Ok(()) } @@ -173,16 +182,29 @@ impl RevisionManager { Ok(()) } + /// Returns the current revision id pub fn rev_id(&self) -> i64 { self.rev_id_counter.value() } + pub async fn next_sync_rev_id(&self) -> Option { + self.rev_persistence.next_sync_rev_id().await + } + pub fn next_rev_id_pair(&self) -> (i64, i64) { let cur = self.rev_id_counter.value(); - let next = self.rev_id_counter.next(); + let next = self.rev_id_counter.next_id(); (cur, next) } + pub fn number_of_sync_revisions(&self) -> usize { + self.rev_persistence.number_of_sync_records() + } + + pub fn number_of_revisions_in_disk(&self) -> usize { + self.rev_persistence.number_of_records_in_disk() + } + pub async fn get_revisions_in_range(&self, range: RevisionRange) -> Result, FlowyError> { let revisions = self.rev_persistence.revisions_in_range(&range).await?; Ok(revisions) @@ -197,7 +219,7 @@ impl RevisionManager { } } -impl WSDataProviderDataSource for Arc { +impl WSDataProviderDataSource for Arc> { fn next_revision(&self) -> FutureResult, FlowyError> { let rev_manager = self.clone(); FutureResult::new(async move { rev_manager.next_sync_revision().await }) @@ -214,25 +236,28 @@ impl WSDataProviderDataSource for Arc { } #[cfg(feature = "flowy_unit_test")] -impl RevisionManager { - pub async fn revision_cache(&self) -> Arc { +impl RevisionManager { + pub async fn revision_cache(&self) -> Arc> { self.rev_persistence.clone() } pub fn ack_notify(&self) -> tokio::sync::broadcast::Receiver { self.rev_ack_notifier.subscribe() } + pub fn get_all_revision_records(&self) -> FlowyResult> { + self.rev_persistence.load_all_records(&self.object_id) + } } -pub struct RevisionLoader { +pub struct RevisionLoader { pub object_id: String, pub user_id: String, pub cloud: Option>, - pub rev_persistence: Arc, + pub rev_persistence: Arc>, } -impl RevisionLoader { +impl RevisionLoader { pub async fn load(&self) -> Result<(Vec, i64), FlowyError> { - let records = self.rev_persistence.batch_get(&self.object_id)?; + let records = self.rev_persistence.load_all_records(&self.object_id)?; let revisions: Vec; let mut rev_id = 0; if records.is_empty() && self.cloud.is_some() { @@ -264,4 +289,99 @@ impl RevisionLoader { Ok((revisions, rev_id)) } + + pub async fn load_revisions(&self) -> Result, FlowyError> { + let records = self.rev_persistence.load_all_records(&self.object_id)?; + let revisions = records.into_iter().map(|record| record.revision).collect::<_>(); + Ok(revisions) + } +} + +/// Represents as the md5 of the revision object after applying the +/// revision. For example, RevisionMD5 will be the md5 of the document +/// content. +#[derive(Debug, Clone)] +pub struct RevisionMD5(String); + +impl RevisionMD5 { + pub fn from_bytes>(bytes: T) -> Result { + Ok(RevisionMD5(md5(bytes))) + } + + pub fn into_inner(self) -> String { + self.0 + } + + pub fn is_equal(&self, s: &str) -> bool { + self.0 == s + } +} + +impl std::convert::From for String { + fn from(md5: RevisionMD5) -> Self { + md5.0 + } +} + +impl std::convert::From<&str> for RevisionMD5 { + fn from(s: &str) -> Self { + Self(s.to_owned()) + } +} +impl std::convert::From for RevisionMD5 { + fn from(s: String) -> Self { + Self(s) + } +} + +impl std::ops::Deref for RevisionMD5 { + type Target = String; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl PartialEq for RevisionMD5 { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl std::cmp::Eq for RevisionMD5 {} + +fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { + let mut rev_id = 0; + revisions.iter().for_each(|revision| { + if rev_id < revision.rev_id { + rev_id = revision.rev_id; + } + }); + + if rev_id > 0 { + (rev_id - 1, rev_id) + } else { + (0, rev_id) + } +} + +#[derive(Debug)] +pub struct RevIdCounter(pub AtomicI64); + +impl RevIdCounter { + pub fn new(n: i64) -> Self { + Self(AtomicI64::new(n)) + } + + pub fn next_id(&self) -> i64 { + let _ = self.0.fetch_add(1, SeqCst); + self.value() + } + pub fn value(&self) -> i64 { + self.0.load(SeqCst) + } + + pub fn set(&self, n: i64) { + let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); + } } diff --git a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs index 0c0875d6a5..06de10b2d8 100644 --- a/frontend/rust-lib/flowy-revision/src/rev_persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/rev_persistence.rs @@ -1,13 +1,12 @@ use crate::cache::{ - disk::{RevisionChangeset, RevisionDiskCache, SQLiteDocumentRevisionPersistence}, + disk::{RevisionChangeset, RevisionDiskCache}, memory::RevisionMemoryCacheDelegate, }; -use crate::disk::{RevisionRecord, RevisionState, SQLiteGridBlockRevisionPersistence}; +use crate::disk::{RevisionState, SyncRecord}; use crate::memory::RevisionMemoryCache; -use crate::RevisionCompress; -use flowy_database::ConnectionPool; +use crate::RevisionMergeable; use flowy_error::{internal_error, FlowyError, FlowyResult}; -use flowy_sync::entities::revision::{Revision, RevisionRange}; +use flowy_http_model::revision::{Revision, RevisionRange}; use std::collections::VecDeque; use std::{borrow::Cow, sync::Arc}; use tokio::sync::RwLock; @@ -15,31 +14,73 @@ use tokio::task::spawn_blocking; pub const REVISION_WRITE_INTERVAL_IN_MILLIS: u64 = 600; -pub struct RevisionPersistence { - user_id: String, - object_id: String, - disk_cache: Arc>, - memory_cache: Arc, - sync_seq: RwLock, +#[derive(Clone)] +pub struct RevisionPersistenceConfiguration { + merge_threshold: usize, + merge_lagging: bool, } -impl RevisionPersistence { - pub fn new(user_id: &str, object_id: &str, disk_cache: C) -> RevisionPersistence +impl RevisionPersistenceConfiguration { + pub fn new(merge_threshold: usize, merge_lagging: bool) -> Self { + debug_assert!(merge_threshold > 1); + if merge_threshold > 1 { + Self { + merge_threshold, + merge_lagging, + } + } else { + Self { + merge_threshold: 100, + merge_lagging, + } + } + } +} + +impl std::default::Default for RevisionPersistenceConfiguration { + fn default() -> Self { + Self { + merge_threshold: 100, + merge_lagging: false, + } + } +} + +pub struct RevisionPersistence { + user_id: String, + object_id: String, + disk_cache: Arc>, + memory_cache: Arc, + sync_seq: RwLock, + configuration: RevisionPersistenceConfiguration, +} + +impl RevisionPersistence +where + Connection: 'static, +{ + pub fn new( + user_id: &str, + object_id: &str, + disk_cache: C, + configuration: RevisionPersistenceConfiguration, + ) -> RevisionPersistence where - C: 'static + RevisionDiskCache, + C: 'static + RevisionDiskCache, { - let disk_cache = Arc::new(disk_cache) as Arc>; - Self::from_disk_cache(user_id, object_id, disk_cache) + let disk_cache = Arc::new(disk_cache) as Arc>; + Self::from_disk_cache(user_id, object_id, disk_cache, configuration) } pub fn from_disk_cache( user_id: &str, object_id: &str, - disk_cache: Arc>, - ) -> RevisionPersistence { + disk_cache: Arc>, + configuration: RevisionPersistenceConfiguration, + ) -> RevisionPersistence { let object_id = object_id.to_owned(); let user_id = user_id.to_owned(); - let sync_seq = RwLock::new(RevisionSyncSequence::new()); + let sync_seq = RwLock::new(DeferSyncSequence::new()); let memory_cache = Arc::new(RevisionMemoryCache::new(&object_id, Arc::new(disk_cache.clone()))); Self { user_id, @@ -47,6 +88,7 @@ impl RevisionPersistence { disk_cache, memory_cache, sync_seq, + configuration, } } @@ -62,7 +104,43 @@ impl RevisionPersistence { pub(crate) async fn sync_revision(&self, revision: &Revision) -> FlowyResult<()> { tracing::Span::current().record("rev_id", &revision.rev_id); self.add(revision.clone(), RevisionState::Sync, false).await?; - self.sync_seq.write().await.add(revision.rev_id)?; + self.sync_seq.write().await.recv(revision.rev_id)?; + Ok(()) + } + + #[tracing::instrument(level = "trace", skip_all, err)] + pub async fn compact_lagging_revisions<'a>( + &'a self, + rev_compress: &Arc, + ) -> FlowyResult<()> { + if !self.configuration.merge_lagging { + return Ok(()); + } + + let mut sync_seq = self.sync_seq.write().await; + let compact_seq = sync_seq.compact(); + if !compact_seq.is_empty() { + let range = RevisionRange { + start: *compact_seq.front().unwrap(), + end: *compact_seq.back().unwrap(), + }; + + let revisions = self.revisions_in_range(&range).await?; + let rev_ids = range.to_rev_ids(); + debug_assert_eq!(range.len() as usize, revisions.len()); + // compact multiple revisions into one + let merged_revision = rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; + tracing::Span::current().record("rev_id", &merged_revision.rev_id); + + let record = SyncRecord { + revision: merged_revision, + state: RevisionState::Sync, + write_to_disk: true, + }; + let _ = self + .disk_cache + .delete_and_insert_records(&self.object_id, Some(rev_ids), vec![record])?; + } Ok(()) } @@ -70,44 +148,46 @@ impl RevisionPersistence { #[tracing::instrument(level = "trace", skip_all, fields(rev_id, compact_range, object_id=%self.object_id), err)] pub(crate) async fn add_sync_revision<'a>( &'a self, - revision: &'a Revision, - rev_compress: &Arc, + new_revision: &'a Revision, + rev_compress: &Arc, ) -> FlowyResult { - let mut sync_seq_write_guard = self.sync_seq.write().await; - let result = sync_seq_write_guard.compact(); - match result { - None => { - tracing::Span::current().record("rev_id", &revision.rev_id); - self.add(revision.clone(), RevisionState::Sync, true).await?; - sync_seq_write_guard.add(revision.rev_id)?; - Ok(revision.rev_id) - } - Some((range, mut compact_seq)) => { - tracing::Span::current().record("compact_range", &format!("{}", range).as_str()); - let mut revisions = self.revisions_in_range(&range).await?; - if range.to_rev_ids().len() != revisions.len() { - debug_assert_eq!(range.to_rev_ids().len(), revisions.len()); - } + let mut sync_seq = self.sync_seq.write().await; + let compact_length = sync_seq.compact_length; - // append the new revision - revisions.push(revision.clone()); + // Before the new_revision is pushed into the sync_seq, we check if the current `compact_length` of the + // sync_seq is less equal to or greater than the merge threshold. If yes, it's needs to merged + // with the new_revision into one revision. + let mut compact_seq = VecDeque::default(); + // tracing::info!("{}", compact_seq) + if compact_length >= self.configuration.merge_threshold - 1 { + compact_seq.extend(sync_seq.compact()); + } + if !compact_seq.is_empty() { + let range = RevisionRange { + start: *compact_seq.front().unwrap(), + end: *compact_seq.back().unwrap(), + }; - // compact multiple revisions into one - let compact_revision = rev_compress.compress_revisions(&self.user_id, &self.object_id, revisions)?; - let rev_id = compact_revision.rev_id; - tracing::Span::current().record("rev_id", &rev_id); + tracing::Span::current().record("compact_range", &format!("{}", range).as_str()); + let mut revisions = self.revisions_in_range(&range).await?; + debug_assert_eq!(range.len() as usize, revisions.len()); + // append the new revision + revisions.push(new_revision.clone()); - // insert new revision - compact_seq.push_back(rev_id); + // compact multiple revisions into one + let merged_revision = rev_compress.merge_revisions(&self.user_id, &self.object_id, revisions)?; + let rev_id = merged_revision.rev_id; + tracing::Span::current().record("rev_id", &merged_revision.rev_id); + let _ = sync_seq.recv(merged_revision.rev_id)?; - // replace the revisions in range with compact revision - self.compact(&range, compact_revision).await?; - // - debug_assert_eq!(compact_seq.len(), 2); - debug_assert_eq!(sync_seq_write_guard.len(), compact_seq.len()); - sync_seq_write_guard.reset(compact_seq); - Ok(rev_id) - } + // replace the revisions in range with compact revision + self.compact(&range, merged_revision).await?; + Ok(rev_id) + } else { + tracing::Span::current().record("rev_id", &new_revision.rev_id); + self.add(new_revision.clone(), RevisionState::Sync, true).await?; + sync_seq.merge_recv(new_revision.rev_id)?; + Ok(new_revision.rev_id) } } @@ -126,12 +206,30 @@ impl RevisionPersistence { } } + pub(crate) async fn next_sync_rev_id(&self) -> Option { + self.sync_seq.read().await.next_rev_id() + } + + pub(crate) fn number_of_sync_records(&self) -> usize { + self.memory_cache.number_of_sync_records() + } + + pub(crate) fn number_of_records_in_disk(&self) -> usize { + match self.disk_cache.read_revision_records(&self.object_id, None) { + Ok(records) => records.len(), + Err(e) => { + tracing::error!("Read revision records failed: {:?}", e); + 0 + } + } + } + /// The cache gets reset while it conflicts with the remote revisions. #[tracing::instrument(level = "trace", skip(self, revisions), err)] pub(crate) async fn reset(&self, revisions: Vec) -> FlowyResult<()> { let records = revisions .into_iter() - .map(|revision| RevisionRecord { + .map(|revision| SyncRecord { revision, state: RevisionState::Sync, write_to_disk: false, @@ -151,7 +249,7 @@ impl RevisionPersistence { tracing::warn!("Duplicate revision: {}:{}-{:?}", self.object_id, revision.rev_id, state); return Ok(()); } - let record = RevisionRecord { + let record = SyncRecord { revision, state, write_to_disk, @@ -167,12 +265,11 @@ impl RevisionPersistence { let _ = self .disk_cache .delete_revision_records(&self.object_id, Some(rev_ids))?; - self.add(new_revision, RevisionState::Sync, true).await?; Ok(()) } - pub async fn get(&self, rev_id: i64) -> Option { + pub async fn get(&self, rev_id: i64) -> Option { match self.memory_cache.get(&rev_id).await { None => match self .disk_cache @@ -192,8 +289,8 @@ impl RevisionPersistence { } } - pub fn batch_get(&self, doc_id: &str) -> FlowyResult> { - self.disk_cache.read_revision_records(doc_id, None) + pub fn load_all_records(&self, object_id: &str) -> FlowyResult> { + self.disk_cache.read_revision_records(object_id, None) } // Read the revision which rev_id >= range.start && rev_id <= range.end @@ -224,22 +321,8 @@ impl RevisionPersistence { } } -pub fn mk_text_block_revision_disk_cache( - user_id: &str, - pool: Arc, -) -> Arc> { - Arc::new(SQLiteDocumentRevisionPersistence::new(user_id, pool)) -} - -pub fn mk_grid_block_revision_disk_cache( - user_id: &str, - pool: Arc, -) -> Arc> { - Arc::new(SQLiteGridBlockRevisionPersistence::new(user_id, pool)) -} - -impl RevisionMemoryCacheDelegate for Arc> { - fn checkpoint_tick(&self, mut records: Vec) -> FlowyResult<()> { +impl RevisionMemoryCacheDelegate for Arc> { + fn send_sync(&self, mut records: Vec) -> FlowyResult<()> { records.retain(|record| record.write_to_disk); if !records.is_empty() { tracing::Span::current().record( @@ -265,27 +348,48 @@ impl RevisionMemoryCacheDelegate for Arc); -impl RevisionSyncSequence { +struct DeferSyncSequence { + rev_ids: VecDeque, + compact_index: Option, + compact_length: usize, +} + +impl DeferSyncSequence { fn new() -> Self { - RevisionSyncSequence::default() + DeferSyncSequence::default() } - fn add(&mut self, new_rev_id: i64) -> FlowyResult<()> { + /// Pushes the new_rev_id to the end of the list and marks this new_rev_id is mergeable. + /// + /// When calling `compact` method, it will return a list of revision ids started from + /// the `compact_start_pos`, and ends with the `compact_length`. + fn merge_recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { + let _ = self.recv(new_rev_id)?; + + self.compact_length += 1; + if self.compact_index.is_none() && !self.rev_ids.is_empty() { + self.compact_index = Some(self.rev_ids.len() - 1); + } + Ok(()) + } + + /// Pushes the new_rev_id to the end of the list. + fn recv(&mut self, new_rev_id: i64) -> FlowyResult<()> { // The last revision's rev_id must be greater than the new one. - if let Some(rev_id) = self.0.back() { + if let Some(rev_id) = self.rev_ids.back() { if *rev_id >= new_rev_id { return Err( FlowyError::internal().context(format!("The new revision's id must be greater than {}", rev_id)) ); } } - self.0.push_back(new_rev_id); + self.rev_ids.push_back(new_rev_id); Ok(()) } + /// Removes the rev_id from the list fn ack(&mut self, rev_id: &i64) -> FlowyResult<()> { - let cur_rev_id = self.0.front().cloned(); + let cur_rev_id = self.rev_ids.front().cloned(); if let Some(pop_rev_id) = cur_rev_id { if &pop_rev_id != rev_id { let desc = format!( @@ -294,38 +398,43 @@ impl RevisionSyncSequence { ); return Err(FlowyError::internal().context(desc)); } - let _ = self.0.pop_front(); + + let mut compact_rev_id = None; + if let Some(compact_index) = self.compact_index { + compact_rev_id = self.rev_ids.get(compact_index).cloned(); + } + + let pop_rev_id = self.rev_ids.pop_front(); + if let (Some(compact_rev_id), Some(pop_rev_id)) = (compact_rev_id, pop_rev_id) { + if compact_rev_id <= pop_rev_id && self.compact_length > 0 { + self.compact_length -= 1; + } + } } Ok(()) } fn next_rev_id(&self) -> Option { - self.0.front().cloned() - } - - fn reset(&mut self, new_seq: VecDeque) { - self.0 = new_seq; + self.rev_ids.front().cloned() } fn clear(&mut self) { - self.0.clear(); - } - - fn len(&self) -> usize { - self.0.len() + self.compact_index = None; + self.compact_length = 0; + self.rev_ids.clear(); } // Compact the rev_ids into one except the current synchronizing rev_id. - fn compact(&self) -> Option<(RevisionRange, VecDeque)> { - // Make sure there are two rev_id going to sync. No need to compact if there is only - // one rev_id in queue. - self.next_rev_id()?; - - let mut new_seq = self.0.clone(); - let mut drained = new_seq.drain(1..).collect::>(); - - let start = drained.pop_front()?; - let end = drained.pop_back().unwrap_or(start); - Some((RevisionRange { start, end }, new_seq)) + fn compact(&mut self) -> VecDeque { + let mut compact_seq = VecDeque::with_capacity(self.rev_ids.len()); + if let Some(start) = self.compact_index { + if start < self.rev_ids.len() { + let seq = self.rev_ids.split_off(start); + compact_seq.extend(seq); + } + } + self.compact_index = None; + self.compact_length = 0; + compact_seq } } diff --git a/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs b/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs index d8d7bae3a6..5ba9f9adb1 100644 --- a/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs +++ b/frontend/rust-lib/flowy-revision/src/snapshot/persistence.rs @@ -2,17 +2,15 @@ #![allow(dead_code)] #![allow(unused_variables)] use crate::{RevisionSnapshotDiskCache, RevisionSnapshotInfo}; -use flowy_database::ConnectionPool; use flowy_error::FlowyResult; -use std::sync::Arc; -pub struct SQLiteRevisionSnapshotPersistence { +pub struct SQLiteRevisionSnapshotPersistence { object_id: String, - pool: Arc, + pool: Connection, } -impl SQLiteRevisionSnapshotPersistence { - pub fn new(object_id: &str, pool: Arc) -> Self { +impl SQLiteRevisionSnapshotPersistence { + pub fn new(object_id: &str, pool: Connection) -> Self { Self { object_id: object_id.to_string(), pool, @@ -20,7 +18,10 @@ impl SQLiteRevisionSnapshotPersistence { } } -impl RevisionSnapshotDiskCache for SQLiteRevisionSnapshotPersistence { +impl RevisionSnapshotDiskCache for SQLiteRevisionSnapshotPersistence +where + Connection: Send + Sync + 'static, +{ fn write_snapshot(&self, object_id: &str, rev_id: i64, data: Vec) -> FlowyResult<()> { todo!() } diff --git a/frontend/rust-lib/flowy-revision/src/ws_manager.rs b/frontend/rust-lib/flowy-revision/src/ws_manager.rs index eb7539c380..eb2ade407c 100644 --- a/frontend/rust-lib/flowy-revision/src/ws_manager.rs +++ b/frontend/rust-lib/flowy-revision/src/ws_manager.rs @@ -2,7 +2,7 @@ use crate::ConflictRevisionSink; use async_stream::stream; use bytes::Bytes; use flowy_error::{FlowyError, FlowyResult}; -use flowy_sync::entities::{ +use flowy_http_model::{ revision::{RevId, Revision, RevisionRange}, ws_data::{ClientRevisionWSData, NewDocumentUser, ServerRevisionWSData, ServerRevisionWSDataType}, }; @@ -28,7 +28,7 @@ pub trait RevisionWSDataStream: Send + Sync { } // The sink provides the data that will be sent through the web socket to the -// backend. +// server. pub trait RevisionWebSocketSink: Send + Sync { fn next(&self) -> FutureResult, FlowyError>; } diff --git a/frontend/rust-lib/flowy-revision/tests/main.rs b/frontend/rust-lib/flowy-revision/tests/main.rs new file mode 100644 index 0000000000..3eb8b414b2 --- /dev/null +++ b/frontend/rust-lib/flowy-revision/tests/main.rs @@ -0,0 +1 @@ +mod revision_test; diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs new file mode 100644 index 0000000000..e530b3c01e --- /dev/null +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/local_revision_test.rs @@ -0,0 +1,318 @@ +use crate::revision_test::script::{RevisionScript::*, RevisionTest}; + +#[tokio::test] +async fn revision_sync_test() { + let test = RevisionTest::new().await; + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + + test.run_script(AddLocalRevision { + content: "123".to_string(), + base_rev_id, + rev_id, + }) + .await; + + test.run_script(AssertNextSyncRevisionId { rev_id: Some(rev_id) }).await; + test.run_script(AckRevision { rev_id }).await; + test.run_script(AssertNextSyncRevisionId { rev_id: None }).await; +} + +#[tokio::test] +async fn revision_compress_2_revisions_with_2_threshold_test() { + let test = RevisionTest::new_with_configuration(2).await; + + test.run_script(AddLocalRevision2 { + content: "123".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }) + .await; + + test.run_script(AddLocalRevision2 { + content: "456".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }) + .await; + + test.run_scripts(vec![ + AssertNextSyncRevisionId { rev_id: Some(1) }, + AckRevision { rev_id: 1 }, + AssertNextSyncRevisionId { rev_id: None }, + ]) + .await; +} + +#[tokio::test] +async fn revision_compress_4_revisions_with_threshold_2_test() { + let test = RevisionTest::new_with_configuration(2).await; + let (base_rev_id, rev_id_1) = test.next_rev_id_pair(); + + test.run_script(AddLocalRevision { + content: "1".to_string(), + base_rev_id, + rev_id: rev_id_1, + }) + .await; + + let (base_rev_id, rev_id_2) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "2".to_string(), + base_rev_id, + rev_id: rev_id_2, + }) + .await; + + let (base_rev_id, rev_id_3) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "3".to_string(), + base_rev_id, + rev_id: rev_id_3, + }) + .await; + + let (base_rev_id, rev_id_4) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "4".to_string(), + base_rev_id, + rev_id: rev_id_4, + }) + .await; + + // rev_id_2,rev_id_3,rev_id4 will be merged into rev_id_1 + test.run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 2 }, + AssertNextSyncRevisionId { rev_id: Some(rev_id_1) }, + AssertNextSyncRevisionContent { + expected: "12".to_string(), + }, + AckRevision { rev_id: rev_id_1 }, + AssertNextSyncRevisionId { rev_id: Some(rev_id_2) }, + AssertNextSyncRevisionContent { + expected: "34".to_string(), + }, + ]) + .await; +} + +#[tokio::test] +async fn revision_compress_8_revisions_with_threshold_4_test() { + let test = RevisionTest::new_with_configuration(4).await; + let (base_rev_id, rev_id_1) = test.next_rev_id_pair(); + + test.run_script(AddLocalRevision { + content: "1".to_string(), + base_rev_id, + rev_id: rev_id_1, + }) + .await; + + let (base_rev_id, rev_id_2) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "2".to_string(), + base_rev_id, + rev_id: rev_id_2, + }) + .await; + + let (base_rev_id, rev_id_3) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "3".to_string(), + base_rev_id, + rev_id: rev_id_3, + }) + .await; + + let (base_rev_id, rev_id_4) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "4".to_string(), + base_rev_id, + rev_id: rev_id_4, + }) + .await; + + let (base_rev_id, rev_id_a) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "a".to_string(), + base_rev_id, + rev_id: rev_id_a, + }) + .await; + + let (base_rev_id, rev_id_b) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "b".to_string(), + base_rev_id, + rev_id: rev_id_b, + }) + .await; + + let (base_rev_id, rev_id_c) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "c".to_string(), + base_rev_id, + rev_id: rev_id_c, + }) + .await; + + let (base_rev_id, rev_id_d) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: "d".to_string(), + base_rev_id, + rev_id: rev_id_d, + }) + .await; + + test.run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 2 }, + AssertNextSyncRevisionId { rev_id: Some(rev_id_1) }, + AssertNextSyncRevisionContent { + expected: "1234".to_string(), + }, + AckRevision { rev_id: rev_id_1 }, + AssertNextSyncRevisionId { rev_id: Some(rev_id_a) }, + AssertNextSyncRevisionContent { + expected: "abcd".to_string(), + }, + AckRevision { rev_id: rev_id_a }, + AssertNextSyncRevisionId { rev_id: None }, + ]) + .await; +} + +#[tokio::test] +async fn revision_merge_per_5_revision_test() { + let test = RevisionTest::new_with_configuration(5).await; + for i in 0..20 { + let content = format!("{}", i); + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content, + base_rev_id, + rev_id, + }) + .await; + } + + test.run_scripts(vec![ + AssertNumberOfSyncRevisions { num: 4 }, + AssertNextSyncRevisionContent { + expected: "01234".to_string(), + }, + AckRevision { rev_id: 1 }, + AssertNextSyncRevisionContent { + expected: "56789".to_string(), + }, + AckRevision { rev_id: 2 }, + AssertNextSyncRevisionContent { + expected: "1011121314".to_string(), + }, + AckRevision { rev_id: 3 }, + AssertNextSyncRevisionContent { + expected: "1516171819".to_string(), + }, + AckRevision { rev_id: 4 }, + AssertNextSyncRevisionId { rev_id: None }, + ]) + .await; +} + +#[tokio::test] +async fn revision_merge_per_100_revision_test() { + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..1000 { + let content = format!("{}", i); + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content, + base_rev_id, + rev_id, + }) + .await; + } + + test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 10 }]).await; +} + +#[tokio::test] +async fn revision_merge_per_100_revision_test2() { + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..50 { + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: format!("{}", i), + base_rev_id, + rev_id, + }) + .await; + } + + test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 50 }]).await; +} + +#[tokio::test] +async fn revision_merge_per_1000_revision_test() { + let test = RevisionTest::new_with_configuration(1000).await; + for i in 0..100000 { + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: format!("{}", i), + base_rev_id, + rev_id, + }) + .await; + } + + test.run_scripts(vec![AssertNumberOfSyncRevisions { num: 100 }]).await; +} + +#[tokio::test] +async fn revision_compress_revision_test() { + let test = RevisionTest::new_with_configuration(2).await; + + test.run_scripts(vec![ + AddLocalRevision2 { + content: "1".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AddLocalRevision2 { + content: "2".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AddLocalRevision2 { + content: "3".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AddLocalRevision2 { + content: "4".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AssertNumberOfSyncRevisions { num: 2 }, + ]) + .await; +} +#[tokio::test] +async fn revision_compress_revision_while_recv_ack_test() { + let test = RevisionTest::new_with_configuration(2).await; + test.run_scripts(vec![ + AddLocalRevision2 { + content: "1".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AckRevision { rev_id: 1 }, + AddLocalRevision2 { + content: "2".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AckRevision { rev_id: 2 }, + AddLocalRevision2 { + content: "3".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AckRevision { rev_id: 3 }, + AddLocalRevision2 { + content: "4".to_string(), + pair_rev_id: test.next_rev_id_pair(), + }, + AssertNumberOfSyncRevisions { num: 4 }, + ]) + .await; +} diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs new file mode 100644 index 0000000000..f0362f1436 --- /dev/null +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/mod.rs @@ -0,0 +1,3 @@ +mod local_revision_test; +mod revision_disk_test; +mod script; diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs new file mode 100644 index 0000000000..aff0de8a11 --- /dev/null +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/revision_disk_test.rs @@ -0,0 +1,104 @@ +use crate::revision_test::script::RevisionScript::*; +use crate::revision_test::script::{InvalidRevisionObject, RevisionTest}; + +#[tokio::test] +async fn revision_write_to_disk_test() { + let test = RevisionTest::new_with_configuration(2).await; + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + + test.run_script(AddLocalRevision { + content: "123".to_string(), + base_rev_id, + rev_id, + }) + .await; + + test.run_scripts(vec![ + AssertNumberOfRevisionsInDisk { num: 0 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 1 }, + ]) + .await; +} + +#[tokio::test] +async fn revision_write_to_disk_with_merge_test() { + let test = RevisionTest::new_with_configuration(100).await; + for i in 0..1000 { + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_script(AddLocalRevision { + content: format!("{}", i), + base_rev_id, + rev_id, + }) + .await; + } + + test.run_scripts(vec![ + AssertNumberOfRevisionsInDisk { num: 0 }, + AssertNumberOfSyncRevisions { num: 10 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 10 }, + ]) + .await; +} + +#[tokio::test] +async fn revision_read_from_disk_test() { + let test = RevisionTest::new_with_configuration(2).await; + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_scripts(vec![ + AddLocalRevision { + content: "123".to_string(), + base_rev_id, + rev_id, + }, + AssertNumberOfRevisionsInDisk { num: 0 }, + WaitWhenWriteToDisk, + AssertNumberOfRevisionsInDisk { num: 1 }, + ]) + .await; + + let test = RevisionTest::new_with_other(test).await; + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_scripts(vec![ + AssertNextSyncRevisionId { rev_id: Some(1) }, + AddLocalRevision { + content: "456".to_string(), + base_rev_id, + rev_id, + }, + AckRevision { rev_id: 1 }, + AssertNextSyncRevisionId { rev_id: Some(rev_id) }, + ]) + .await; +} + +#[tokio::test] +async fn revision_read_from_disk_with_invalid_record_test() { + let test = RevisionTest::new_with_configuration(2).await; + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_scripts(vec![AddLocalRevision { + content: "123".to_string(), + base_rev_id, + rev_id, + }]) + .await; + + let (base_rev_id, rev_id) = test.next_rev_id_pair(); + test.run_scripts(vec![ + AddInvalidLocalRevision { + bytes: InvalidRevisionObject::new().to_bytes(), + base_rev_id, + rev_id, + }, + WaitWhenWriteToDisk, + ]) + .await; + + let test = RevisionTest::new_with_other(test).await; + test.run_scripts(vec![AssertNextSyncRevisionContent { + expected: "123".to_string(), + }]) + .await; +} diff --git a/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs b/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs new file mode 100644 index 0000000000..42f361b33d --- /dev/null +++ b/frontend/rust-lib/flowy-revision/tests/revision_test/script.rs @@ -0,0 +1,377 @@ +use bytes::Bytes; +use flowy_error::{internal_error, FlowyError, FlowyResult}; +use flowy_revision::disk::{RevisionChangeset, RevisionDiskCache, SyncRecord}; +use flowy_revision::{ + RevisionManager, RevisionMergeable, RevisionObjectDeserializer, RevisionPersistence, + RevisionPersistenceConfiguration, RevisionSnapshotDiskCache, RevisionSnapshotInfo, + REVISION_WRITE_INTERVAL_IN_MILLIS, +}; + +use flowy_http_model::revision::{Revision, RevisionRange}; +use flowy_http_model::util::md5; +use nanoid::nanoid; +use parking_lot::RwLock; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use std::time::Duration; + +pub enum RevisionScript { + AddLocalRevision { + content: String, + base_rev_id: i64, + rev_id: i64, + }, + AddLocalRevision2 { + content: String, + pair_rev_id: (i64, i64), + }, + AddInvalidLocalRevision { + bytes: Vec, + base_rev_id: i64, + rev_id: i64, + }, + AckRevision { + rev_id: i64, + }, + AssertNextSyncRevisionId { + rev_id: Option, + }, + AssertNumberOfSyncRevisions { + num: usize, + }, + AssertNumberOfRevisionsInDisk { + num: usize, + }, + AssertNextSyncRevisionContent { + expected: String, + }, + WaitWhenWriteToDisk, +} + +pub struct RevisionTest { + user_id: String, + object_id: String, + configuration: RevisionPersistenceConfiguration, + rev_manager: Arc>, +} + +impl RevisionTest { + pub async fn new() -> Self { + Self::new_with_configuration(2).await + } + + pub async fn new_with_configuration(merge_threshold: i64) -> Self { + let user_id = nanoid!(10); + let object_id = nanoid!(6); + let configuration = RevisionPersistenceConfiguration::new(merge_threshold as usize, false); + let disk_cache = RevisionDiskCacheMock::new(vec![]); + let persistence = RevisionPersistence::new(&user_id, &object_id, disk_cache, configuration.clone()); + let compress = RevisionCompressMock {}; + let snapshot = RevisionSnapshotMock {}; + let mut rev_manager = RevisionManager::new(&user_id, &object_id, persistence, compress, snapshot); + rev_manager.initialize::(None).await.unwrap(); + Self { + user_id, + object_id, + configuration, + rev_manager: Arc::new(rev_manager), + } + } + + pub async fn new_with_other(old_test: RevisionTest) -> Self { + let records = old_test.rev_manager.get_all_revision_records().unwrap(); + let disk_cache = RevisionDiskCacheMock::new(records); + let configuration = old_test.configuration; + let persistence = RevisionPersistence::new( + &old_test.user_id, + &old_test.object_id, + disk_cache, + configuration.clone(), + ); + + let compress = RevisionCompressMock {}; + let snapshot = RevisionSnapshotMock {}; + let mut rev_manager = + RevisionManager::new(&old_test.user_id, &old_test.object_id, persistence, compress, snapshot); + rev_manager.initialize::(None).await.unwrap(); + Self { + user_id: old_test.user_id, + object_id: old_test.object_id, + configuration, + rev_manager: Arc::new(rev_manager), + } + } + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub fn next_rev_id_pair(&self) -> (i64, i64) { + self.rev_manager.next_rev_id_pair() + } + + pub async fn run_script(&self, script: RevisionScript) { + match script { + RevisionScript::AddLocalRevision { + content, + base_rev_id, + rev_id, + } => { + let object = RevisionObjectMock::new(&content); + let bytes = object.to_bytes(); + let md5 = md5(&bytes); + let revision = Revision::new( + &self.rev_manager.object_id, + base_rev_id, + rev_id, + Bytes::from(bytes), + md5, + ); + self.rev_manager.add_local_revision(&revision).await.unwrap(); + } + RevisionScript::AddLocalRevision2 { content, pair_rev_id } => { + let object = RevisionObjectMock::new(&content); + let bytes = object.to_bytes(); + let md5 = md5(&bytes); + let revision = Revision::new( + &self.rev_manager.object_id, + pair_rev_id.0, + pair_rev_id.1, + Bytes::from(bytes), + md5, + ); + self.rev_manager.add_local_revision(&revision).await.unwrap(); + } + RevisionScript::AddInvalidLocalRevision { + bytes, + base_rev_id, + rev_id, + } => { + let md5 = md5(&bytes); + let revision = Revision::new( + &self.rev_manager.object_id, + base_rev_id, + rev_id, + Bytes::from(bytes), + md5, + ); + self.rev_manager.add_local_revision(&revision).await.unwrap(); + } + RevisionScript::AckRevision { rev_id } => { + // + self.rev_manager.ack_revision(rev_id).await.unwrap() + } + RevisionScript::AssertNextSyncRevisionId { rev_id } => { + assert_eq!(self.rev_manager.next_sync_rev_id().await, rev_id) + } + RevisionScript::AssertNumberOfSyncRevisions { num } => { + assert_eq!(self.rev_manager.number_of_sync_revisions(), num) + } + RevisionScript::AssertNumberOfRevisionsInDisk { num } => { + assert_eq!(self.rev_manager.number_of_revisions_in_disk(), num) + } + RevisionScript::AssertNextSyncRevisionContent { expected } => { + // + let rev_id = self.rev_manager.next_sync_rev_id().await.unwrap(); + let revision = self.rev_manager.get_revision(rev_id).await.unwrap(); + let object = RevisionObjectMock::from_bytes(&revision.bytes).unwrap(); + assert_eq!(object.content, expected); + } + RevisionScript::WaitWhenWriteToDisk => { + let milliseconds = 2 * REVISION_WRITE_INTERVAL_IN_MILLIS; + tokio::time::sleep(Duration::from_millis(milliseconds)).await; + } + } + } +} + +pub struct RevisionDiskCacheMock { + records: RwLock>, +} + +impl RevisionDiskCacheMock { + pub fn new(records: Vec) -> Self { + Self { + records: RwLock::new(records), + } + } +} + +impl RevisionDiskCache for RevisionDiskCacheMock { + type Error = FlowyError; + + fn create_revision_records(&self, revision_records: Vec) -> Result<(), Self::Error> { + self.records.write().extend(revision_records); + Ok(()) + } + + fn get_connection(&self) -> Result { + todo!() + } + + fn read_revision_records( + &self, + _object_id: &str, + rev_ids: Option>, + ) -> Result, Self::Error> { + match rev_ids { + None => Ok(self.records.read().clone()), + Some(rev_ids) => Ok(self + .records + .read() + .iter() + .filter(|record| rev_ids.contains(&record.revision.rev_id)) + .cloned() + .collect::>()), + } + } + + fn read_revision_records_with_range( + &self, + _object_id: &str, + range: &RevisionRange, + ) -> Result, Self::Error> { + let read_guard = self.records.read(); + let records = range + .iter() + .flat_map(|rev_id| { + read_guard + .iter() + .find(|record| record.revision.rev_id == rev_id) + .cloned() + }) + .collect::>(); + Ok(records) + } + + fn update_revision_record(&self, changesets: Vec) -> FlowyResult<()> { + for changeset in changesets { + if let Some(record) = self + .records + .write() + .iter_mut() + .find(|record| record.revision.rev_id == *changeset.rev_id.as_ref()) + { + record.state = changeset.state; + } + } + Ok(()) + } + + fn delete_revision_records(&self, _object_id: &str, rev_ids: Option>) -> Result<(), Self::Error> { + match rev_ids { + None => {} + Some(rev_ids) => { + for rev_id in rev_ids { + if let Some(index) = self + .records + .read() + .iter() + .position(|record| record.revision.rev_id == rev_id) + { + self.records.write().remove(index); + } + } + } + } + Ok(()) + } + + fn delete_and_insert_records( + &self, + _object_id: &str, + _deleted_rev_ids: Option>, + _inserted_records: Vec, + ) -> Result<(), Self::Error> { + todo!() + } +} + +pub struct RevisionConnectionMock {} +pub struct RevisionSnapshotMock {} +impl RevisionSnapshotDiskCache for RevisionSnapshotMock { + fn write_snapshot(&self, _object_id: &str, _rev_id: i64, _data: Vec) -> FlowyResult<()> { + todo!() + } + + fn read_snapshot(&self, _object_id: &str, _rev_id: i64) -> FlowyResult { + todo!() + } +} + +pub struct RevisionCompressMock {} + +impl RevisionMergeable for RevisionCompressMock { + fn combine_revisions(&self, revisions: Vec) -> FlowyResult { + let mut object = RevisionObjectMock::new(""); + for revision in revisions { + if let Ok(other) = RevisionObjectMock::from_bytes(&revision.bytes) { + let _ = object.compose(other)?; + } + } + Ok(Bytes::from(object.to_bytes())) + } +} + +#[derive(Serialize, Deserialize)] +pub struct InvalidRevisionObject { + data: String, +} + +impl InvalidRevisionObject { + pub fn new() -> Self { + InvalidRevisionObject { data: "".to_string() } + } + pub(crate) fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).unwrap() + } + + // fn from_bytes(bytes: &[u8]) -> Self { + // serde_json::from_slice(bytes).unwrap() + // } +} + +#[derive(Serialize, Deserialize)] +pub struct RevisionObjectMock { + content: String, +} + +impl RevisionObjectMock { + pub fn new(s: &str) -> Self { + Self { content: s.to_owned() } + } + + pub fn compose(&mut self, other: RevisionObjectMock) -> FlowyResult<()> { + self.content.push_str(other.content.as_str()); + Ok(()) + } + + pub fn to_bytes(&self) -> Vec { + serde_json::to_vec(self).unwrap() + } + + pub fn from_bytes(bytes: &[u8]) -> FlowyResult { + serde_json::from_slice(bytes).map_err(internal_error) + } +} + +pub struct RevisionObjectMockSerde(); +impl RevisionObjectDeserializer for RevisionObjectMockSerde { + type Output = RevisionObjectMock; + + fn deserialize_revisions(_object_id: &str, revisions: Vec) -> FlowyResult { + let mut object = RevisionObjectMock::new(""); + if revisions.is_empty() { + return Ok(object); + } + + for revision in revisions { + if let Ok(revision_object) = RevisionObjectMock::from_bytes(&revision.bytes) { + let _ = object.compose(revision_object)?; + } + } + + Ok(object) + } +} diff --git a/frontend/rust-lib/flowy-sdk/Cargo.toml b/frontend/rust-lib/flowy-sdk/Cargo.toml index 7a1be98efa..cc5b9a1329 100644 --- a/frontend/rust-lib/flowy-sdk/Cargo.toml +++ b/frontend/rust-lib/flowy-sdk/Cargo.toml @@ -12,31 +12,22 @@ flowy-user = { path = "../flowy-user" } flowy-net = { path = "../flowy-net" } flowy-folder = { path = "../flowy-folder", default-features = false } flowy-grid = { path = "../flowy-grid", default-features = false } -flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" } +grid-rev-model = { path = "../../../shared-lib/grid-rev-model" } flowy-database = { path = "../flowy-database" } flowy-document = { path = "../flowy-document", default-features = false } flowy-revision = { path = "../flowy-revision" } +flowy-task = { path = "../flowy-task" } tracing = { version = "0.1" } -log = "0.4.14" futures-core = { version = "0.3", default-features = false } -color-eyre = { version = "0.5", default-features = false } bytes = "1.0" tokio = { version = "1", features = ["rt"] } -parking_lot = "0.11" +parking_lot = "0.12.1" -flowy-sync = { path = "../../../shared-lib/flowy-sync" } +flowy-http-model = { path = "../../../shared-lib/flowy-http-model" } lib-ws = { path = "../../../shared-lib/lib-ws" } lib-infra = { path = "../../../shared-lib/lib-infra" } -[dev-dependencies] -serde = { version = "1.0", features = ["derive"] } -bincode = { version = "1.3" } -protobuf = { version = "2.24.1" } -claim = "0.5.0" -tokio = { version = "1", features = ["full"] } -futures-util = "0.3.15" - [features] http_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"] native_sync = ["flowy-folder/cloud_sync", "flowy-document/cloud_sync"] @@ -45,7 +36,6 @@ dart = [ "flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", - "flowy-sync/dart", "flowy-grid/dart", "flowy-document/dart", ] diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs index 41a25c0c1f..bfafb3d746 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/document_deps.rs @@ -2,14 +2,14 @@ use bytes::Bytes; use flowy_database::ConnectionPool; use flowy_document::{ errors::{internal_error, FlowyError}, - DocumentCloudService, DocumentManager, DocumentUser, + DocumentCloudService, DocumentConfig, DocumentDatabase, DocumentManager, DocumentUser, }; +use flowy_http_model::ws_data::ClientRevisionWSData; use flowy_net::ClientServerConfiguration; use flowy_net::{ http_server::document::DocumentCloudServiceImpl, local_server::LocalServer, ws::connection::FlowyWebSocketConnect, }; use flowy_revision::{RevisionWebSocket, WSStateReceiver}; -use flowy_sync::entities::ws_data::ClientRevisionWSData; use flowy_user::services::UserSession; use futures_core::future::BoxFuture; use lib_infra::future::BoxResultFuture; @@ -23,15 +23,23 @@ impl DocumentDepsResolver { ws_conn: Arc, user_session: Arc, server_config: &ClientServerConfiguration, + document_config: &DocumentConfig, ) -> Arc { - let user = Arc::new(BlockUserImpl(user_session)); + let user = Arc::new(BlockUserImpl(user_session.clone())); let rev_web_socket = Arc::new(DocumentRevisionWebSocket(ws_conn.clone())); let cloud_service: Arc = match local_server { None => Arc::new(DocumentCloudServiceImpl::new(server_config.clone())), Some(local_server) => local_server, }; + let database = Arc::new(DocumentDatabaseImpl(user_session)); - let manager = Arc::new(DocumentManager::new(cloud_service, user, rev_web_socket)); + let manager = Arc::new(DocumentManager::new( + cloud_service, + user, + database, + rev_web_socket, + document_config.clone(), + )); let receiver = Arc::new(DocumentWSMessageReceiverImpl(manager.clone())); ws_conn.add_ws_message_receiver(receiver).unwrap(); @@ -58,7 +66,10 @@ impl DocumentUser for BlockUserImpl { fn token(&self) -> Result { self.0.token() } +} +struct DocumentDatabaseImpl(Arc); +impl DocumentDatabase for DocumentDatabaseImpl { fn db_pool(&self) -> Result, FlowyError> { self.0.db_pool() } diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs index a3dbdb57ac..eeb28e06ec 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/folder_deps.rs @@ -1,7 +1,8 @@ use bytes::Bytes; use flowy_database::ConnectionPool; + use flowy_document::DocumentManager; -use flowy_folder::entities::{ViewDataTypePB, ViewLayoutTypePB}; +use flowy_folder::entities::{ViewDataFormatPB, ViewLayoutTypePB, ViewPB}; use flowy_folder::manager::{ViewDataProcessor, ViewDataProcessorMap}; use flowy_folder::{ errors::{internal_error, FlowyError}, @@ -11,17 +12,16 @@ use flowy_folder::{ use flowy_grid::entities::GridLayout; use flowy_grid::manager::{make_grid_view_data, GridManager}; use flowy_grid::util::{make_default_board, make_default_grid}; -use flowy_grid_data_model::revision::BuildGridContext; +use flowy_http_model::revision::Revision; +use flowy_http_model::ws_data::ClientRevisionWSData; use flowy_net::ClientServerConfiguration; use flowy_net::{ http_server::folder::FolderHttpCloudService, local_server::LocalServer, ws::connection::FlowyWebSocketConnect, }; use flowy_revision::{RevisionWebSocket, WSStateReceiver}; -use flowy_sync::client_document::default::initial_document_str; -use flowy_sync::entities::revision::{RepeatedRevision, Revision}; -use flowy_sync::entities::ws_data::ClientRevisionWSData; use flowy_user::services::UserSession; use futures_core::future::BoxFuture; +use grid_rev_model::BuildGridContext; use lib_infra::future::{BoxResultFuture, FutureResult}; use lib_ws::{WSChannel, WSMessageReceiver, WebSocketRawMessage}; use std::collections::HashMap; @@ -64,16 +64,20 @@ impl FolderDepsResolver { } fn make_view_data_processor( - text_block_manager: Arc, + document_manager: Arc, grid_manager: Arc, ) -> ViewDataProcessorMap { - let mut map: HashMap> = HashMap::new(); + let mut map: HashMap> = HashMap::new(); - let block_data_impl = DocumentViewDataProcessor(text_block_manager); - map.insert(block_data_impl.data_type(), Arc::new(block_data_impl)); + let document_processor = Arc::new(DocumentViewDataProcessor(document_manager)); + document_processor.data_types().into_iter().for_each(|data_type| { + map.insert(data_type, document_processor.clone()); + }); - let grid_data_impl = GridViewDataProcessor(grid_manager); - map.insert(grid_data_impl.data_type(), Arc::new(grid_data_impl)); + let grid_data_impl = Arc::new(GridViewDataProcessor(grid_manager)); + grid_data_impl.data_types().into_iter().for_each(|data_type| { + map.insert(data_type, grid_data_impl.clone()); + }); Arc::new(map) } @@ -138,45 +142,41 @@ impl WSMessageReceiver for FolderWSMessageReceiverImpl { struct DocumentViewDataProcessor(Arc); impl ViewDataProcessor for DocumentViewDataProcessor { - fn initialize(&self) -> FutureResult<(), FlowyError> { - let manager = self.0.clone(); - FutureResult::new(async move { manager.init() }) - } - - fn create_container( + fn create_view( &self, - user_id: &str, + _user_id: &str, view_id: &str, layout: ViewLayoutTypePB, - delta_data: Bytes, + view_data: Bytes, ) -> FutureResult<(), FlowyError> { // Only accept Document type debug_assert_eq!(layout, ViewLayoutTypePB::Document); - let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into(); + let revision = Revision::initial_revision(view_id, view_data); let view_id = view_id.to_string(); let manager = self.0.clone(); + FutureResult::new(async move { - let _ = manager.create_document(view_id, repeated_revision).await?; + let _ = manager.create_document(view_id, vec![revision]).await?; Ok(()) }) } - fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError> { + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { let manager = self.0.clone(); let view_id = view_id.to_string(); FutureResult::new(async move { - let _ = manager.close_document_editor(view_id)?; + let _ = manager.close_document_editor(view_id).await?; Ok(()) }) } - fn get_view_data(&self, view_id: &str) -> FutureResult { - let view_id = view_id.to_string(); + fn get_view_data(&self, view: &ViewPB) -> FutureResult { + let view_id = view.id.clone(); let manager = self.0.clone(); FutureResult::new(async move { let editor = manager.open_document_editor(view_id).await?; - let delta_bytes = Bytes::from(editor.get_operation_str().await?); - Ok(delta_bytes) + let document_data = Bytes::from(editor.duplicate().await?); + Ok(document_data) }) } @@ -185,17 +185,17 @@ impl ViewDataProcessor for DocumentViewDataProcessor { user_id: &str, view_id: &str, layout: ViewLayoutTypePB, + _data_format: ViewDataFormatPB, ) -> FutureResult { debug_assert_eq!(layout, ViewLayoutTypePB::Document); - let user_id = user_id.to_string(); + let _user_id = user_id.to_string(); let view_id = view_id.to_string(); let manager = self.0.clone(); + let document_content = self.0.initial_document_content(); FutureResult::new(async move { - let view_data = initial_document_str(); - let delta_data = Bytes::from(view_data); - let repeated_revision: RepeatedRevision = - Revision::initial_revision(&user_id, &view_id, delta_data.clone()).into(); - let _ = manager.create_document(view_id, repeated_revision).await?; + let delta_data = Bytes::from(document_content); + let revision = Revision::initial_revision(&view_id, delta_data.clone()); + let _ = manager.create_document(view_id, vec![revision]).await?; Ok(delta_data) }) } @@ -211,34 +211,30 @@ impl ViewDataProcessor for DocumentViewDataProcessor { FutureResult::new(async move { Ok(Bytes::from(data)) }) } - fn data_type(&self) -> ViewDataTypePB { - ViewDataTypePB::Text + fn data_types(&self) -> Vec { + vec![ViewDataFormatPB::DeltaFormat, ViewDataFormatPB::TreeFormat] } } struct GridViewDataProcessor(Arc); impl ViewDataProcessor for GridViewDataProcessor { - fn initialize(&self) -> FutureResult<(), FlowyError> { - FutureResult::new(async { Ok(()) }) - } - - fn create_container( + fn create_view( &self, - user_id: &str, + _user_id: &str, view_id: &str, _layout: ViewLayoutTypePB, delta_data: Bytes, ) -> FutureResult<(), FlowyError> { - let repeated_revision: RepeatedRevision = Revision::initial_revision(user_id, view_id, delta_data).into(); + let revision = Revision::initial_revision(view_id, delta_data); let view_id = view_id.to_string(); let grid_manager = self.0.clone(); FutureResult::new(async move { - let _ = grid_manager.create_grid(view_id, repeated_revision).await?; + let _ = grid_manager.create_grid(view_id, vec![revision]).await?; Ok(()) }) } - fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError> { + fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError> { let grid_manager = self.0.clone(); let view_id = view_id.to_string(); FutureResult::new(async move { @@ -247,9 +243,9 @@ impl ViewDataProcessor for GridViewDataProcessor { }) } - fn get_view_data(&self, view_id: &str) -> FutureResult { - let view_id = view_id.to_string(); + fn get_view_data(&self, view: &ViewPB) -> FutureResult { let grid_manager = self.0.clone(); + let view_id = view.id.clone(); FutureResult::new(async move { let editor = grid_manager.open_grid(view_id).await?; let delta_bytes = editor.duplicate_grid().await?; @@ -262,7 +258,9 @@ impl ViewDataProcessor for GridViewDataProcessor { user_id: &str, view_id: &str, layout: ViewLayoutTypePB, + data_format: ViewDataFormatPB, ) -> FutureResult { + debug_assert_eq!(data_format, ViewDataFormatPB::DatabaseFormat); let (build_context, layout) = match layout { ViewLayoutTypePB::Grid => (make_default_grid(), GridLayout::Table), ViewLayoutTypePB::Board => (make_default_board(), GridLayout::Board), @@ -309,7 +307,7 @@ impl ViewDataProcessor for GridViewDataProcessor { }) } - fn data_type(&self) -> ViewDataTypePB { - ViewDataTypePB::Database + fn data_types(&self) -> Vec { + vec![ViewDataFormatPB::DatabaseFormat] } } diff --git a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs index 880bc7031b..d4266918e4 100644 --- a/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs +++ b/frontend/rust-lib/flowy-sdk/src/deps_resolve/grid_deps.rs @@ -3,25 +3,32 @@ use bytes::Bytes; use flowy_database::ConnectionPool; use flowy_grid::manager::{GridManager, GridUser}; use flowy_grid::services::persistence::GridDatabase; +use flowy_http_model::ws_data::ClientRevisionWSData; use flowy_net::ws::connection::FlowyWebSocketConnect; use flowy_revision::{RevisionWebSocket, WSStateReceiver}; -use flowy_sync::entities::ws_data::ClientRevisionWSData; +use flowy_task::TaskDispatcher; use flowy_user::services::UserSession; use futures_core::future::BoxFuture; use lib_infra::future::BoxResultFuture; use lib_ws::{WSChannel, WebSocketRawMessage}; use std::convert::TryInto; use std::sync::Arc; +use tokio::sync::RwLock; pub struct GridDepsResolver(); impl GridDepsResolver { - pub async fn resolve(ws_conn: Arc, user_session: Arc) -> Arc { + pub async fn resolve( + ws_conn: Arc, + user_session: Arc, + task_scheduler: Arc>, + ) -> Arc { let user = Arc::new(GridUserImpl(user_session.clone())); let rev_web_socket = Arc::new(GridRevisionWebSocket(ws_conn)); let grid_manager = Arc::new(GridManager::new( user.clone(), rev_web_socket, + task_scheduler, Arc::new(GridDatabaseImpl(user_session)), )); diff --git a/frontend/rust-lib/flowy-sdk/src/lib.rs b/frontend/rust-lib/flowy-sdk/src/lib.rs index 24a9e4f56c..ec757c9e10 100644 --- a/frontend/rust-lib/flowy-sdk/src/lib.rs +++ b/frontend/rust-lib/flowy-sdk/src/lib.rs @@ -3,7 +3,10 @@ pub mod module; pub use flowy_net::get_client_server_configuration; use crate::deps_resolve::*; -use flowy_document::DocumentManager; + +use flowy_document::entities::DocumentVersionPB; +use flowy_document::{DocumentConfig, DocumentManager}; +use flowy_folder::entities::ViewDataFormatPB; use flowy_folder::{errors::FlowyError, manager::FolderManager}; use flowy_grid::manager::GridManager; use flowy_net::ClientServerConfiguration; @@ -12,11 +15,13 @@ use flowy_net::{ local_server::LocalServer, ws::connection::{listen_on_websocket, FlowyWebSocketConnect}, }; +use flowy_task::{TaskDispatcher, TaskRunner}; use flowy_user::services::{notifier::UserStatus, UserSession, UserSessionConfig}; use lib_dispatch::prelude::*; use lib_dispatch::runtime::tokio_default_runtime; use module::mk_modules; pub use module::*; +use std::time::Duration; use std::{ fmt, sync::{ @@ -24,7 +29,7 @@ use std::{ Arc, }, }; -use tokio::sync::broadcast; +use tokio::sync::{broadcast, RwLock}; static INIT_LOG: AtomicBool = AtomicBool::new(false); @@ -34,27 +39,35 @@ pub struct FlowySDKConfig { root: String, log_filter: String, server_config: ClientServerConfiguration, + pub document: DocumentConfig, } impl fmt::Debug for FlowySDKConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlowySDKConfig") .field("root", &self.root) - .field("server_config", &self.server_config) + .field("server-config", &self.server_config) + .field("document-config", &self.document) .finish() } } impl FlowySDKConfig { - pub fn new(root: &str, server_config: ClientServerConfiguration, name: &str) -> Self { + pub fn new(root: &str, name: &str, server_config: ClientServerConfiguration) -> Self { FlowySDKConfig { name: name.to_owned(), root: root.to_owned(), log_filter: crate_log_filter("info".to_owned()), server_config, + document: DocumentConfig::default(), } } + pub fn with_document_version(mut self, version: DocumentVersionPB) -> Self { + self.document.version = version; + self + } + pub fn log_filter(mut self, level: &str) -> Self { self.log_filter = crate_log_filter(level.to_owned()); self @@ -75,7 +88,7 @@ fn crate_log_filter(level: String) -> String { filters.push(format!("lib_ws={}", level)); filters.push(format!("lib_infra={}", level)); filters.push(format!("flowy_sync={}", level)); - // filters.push(format!("flowy_revision={}", level)); + filters.push(format!("flowy_revision={}", level)); // filters.push(format!("lib_dispatch={}", level)); filters.push(format!("dart_ffi={}", "info")); @@ -87,14 +100,15 @@ fn crate_log_filter(level: String) -> String { #[derive(Clone)] pub struct FlowySDK { #[allow(dead_code)] - config: FlowySDKConfig, + pub config: FlowySDKConfig, pub user_session: Arc, - pub text_block_manager: Arc, + pub document_manager: Arc, pub folder_manager: Arc, pub grid_manager: Arc, - pub dispatcher: Arc, + pub event_dispatcher: Arc, pub ws_conn: Arc, pub local_server: Option>, + pub task_dispatcher: Arc>, } impl FlowySDK { @@ -103,24 +117,30 @@ impl FlowySDK { init_kv(&config.root); tracing::debug!("🔥 {:?}", config); let runtime = tokio_default_runtime().unwrap(); + let task_scheduler = TaskDispatcher::new(Duration::from_secs(2)); + let task_dispatcher = Arc::new(RwLock::new(task_scheduler)); + runtime.spawn(TaskRunner::run(task_dispatcher.clone())); + let (local_server, ws_conn) = mk_local_server(&config.server_config); - let (user_session, text_block_manager, folder_manager, local_server, grid_manager) = runtime.block_on(async { + let (user_session, document_manager, folder_manager, local_server, grid_manager) = runtime.block_on(async { let user_session = mk_user_session(&config, &local_server, &config.server_config); - let text_block_manager = DocumentDepsResolver::resolve( + let document_manager = DocumentDepsResolver::resolve( local_server.clone(), ws_conn.clone(), user_session.clone(), &config.server_config, + &config.document, ); - let grid_manager = GridDepsResolver::resolve(ws_conn.clone(), user_session.clone()).await; + let grid_manager = + GridDepsResolver::resolve(ws_conn.clone(), user_session.clone(), task_dispatcher.clone()).await; let folder_manager = FolderDepsResolver::resolve( local_server.clone(), user_session.clone(), &config.server_config, &ws_conn, - &text_block_manager, + &document_manager, &grid_manager, ) .await; @@ -131,46 +151,57 @@ impl FlowySDK { ws_conn.init().await; ( user_session, - text_block_manager, + document_manager, folder_manager, local_server, grid_manager, ) }); - let dispatcher = Arc::new(EventDispatcher::construct(runtime, || { + let event_dispatcher = Arc::new(EventDispatcher::construct(runtime, || { mk_modules( &ws_conn, &folder_manager, &grid_manager, &user_session, - &text_block_manager, + &document_manager, ) })); - _start_listening(&dispatcher, &ws_conn, &user_session, &folder_manager, &grid_manager); + _start_listening( + &config, + &event_dispatcher, + &ws_conn, + &user_session, + &document_manager, + &folder_manager, + &grid_manager, + ); Self { config, user_session, - text_block_manager, + document_manager, folder_manager, grid_manager, - dispatcher, + event_dispatcher, ws_conn, local_server, + task_dispatcher, } } pub fn dispatcher(&self) -> Arc { - self.dispatcher.clone() + self.event_dispatcher.clone() } } fn _start_listening( - dispatch: &EventDispatcher, + config: &FlowySDKConfig, + event_dispatch: &EventDispatcher, ws_conn: &Arc, user_session: &Arc, + document_manager: &Arc, folder_manager: &Arc, grid_manager: &Arc, ) { @@ -181,20 +212,24 @@ fn _start_listening( let cloned_folder_manager = folder_manager.clone(); let ws_conn = ws_conn.clone(); let user_session = user_session.clone(); + let document_manager = document_manager.clone(); + let config = config.clone(); - dispatch.spawn(async move { + event_dispatch.spawn(async move { user_session.init(); listen_on_websocket(ws_conn.clone()); _listen_user_status( + config, ws_conn.clone(), subscribe_user_status, - folder_manager.clone(), - grid_manager.clone(), + document_manager, + folder_manager, + grid_manager, ) .await; }); - dispatch.spawn(async move { + event_dispatch.spawn(async move { _listen_network_status(subscribe_network_type, cloned_folder_manager).await; }); } @@ -215,8 +250,10 @@ fn mk_local_server( } async fn _listen_user_status( + config: FlowySDKConfig, ws_conn: Arc, mut subscribe: broadcast::Receiver, + document_manager: Arc, folder_manager: Arc, grid_manager: Arc, ) { @@ -226,6 +263,7 @@ async fn _listen_user_status( UserStatus::Login { token, user_id } => { tracing::trace!("User did login"); let _ = folder_manager.initialize(&user_id, &token).await?; + let _ = document_manager.initialize(&user_id).await?; let _ = grid_manager.initialize(&user_id, &token).await?; let _ = ws_conn.start(token, user_id).await?; } @@ -241,7 +279,15 @@ async fn _listen_user_status( } UserStatus::SignUp { profile, ret } => { tracing::trace!("User did sign up"); + + let view_data_type = match config.document.version { + DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, + DocumentVersionPB::V1 => ViewDataFormatPB::TreeFormat, + }; let _ = folder_manager + .initialize_with_new_user(&profile.id, &profile.token, view_data_type) + .await?; + let _ = document_manager .initialize_with_new_user(&profile.id, &profile.token) .await?; @@ -258,7 +304,7 @@ async fn _listen_user_status( match result().await { Ok(_) => {} - Err(e) => log::error!("{}", e), + Err(e) => tracing::error!("{}", e), } } } diff --git a/frontend/rust-lib/flowy-sdk/src/module.rs b/frontend/rust-lib/flowy-sdk/src/module.rs index 58fe1f4f54..e6b89da6a6 100644 --- a/frontend/rust-lib/flowy-sdk/src/module.rs +++ b/frontend/rust-lib/flowy-sdk/src/module.rs @@ -11,20 +11,14 @@ pub fn mk_modules( folder_manager: &Arc, grid_manager: &Arc, user_session: &Arc, - text_block_manager: &Arc, + document_manager: &Arc, ) -> Vec { let user_module = mk_user_module(user_session.clone()); let folder_module = mk_folder_module(folder_manager.clone()); let network_module = mk_network_module(ws_conn.clone()); let grid_module = mk_grid_module(grid_manager.clone()); - let text_block_module = mk_text_block_module(text_block_manager.clone()); - vec![ - user_module, - folder_module, - network_module, - grid_module, - text_block_module, - ] + let document_module = mk_text_block_module(document_manager.clone()); + vec![user_module, folder_module, network_module, grid_module, document_module] } fn mk_user_module(user_session: Arc) -> Module { diff --git a/frontend/rust-lib/flowy-task/Cargo.toml b/frontend/rust-lib/flowy-task/Cargo.toml new file mode 100644 index 0000000000..e8ef930f0a --- /dev/null +++ b/frontend/rust-lib/flowy-task/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "flowy-task" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lib-infra = { path = "../../../shared-lib/lib-infra" } +tokio = {version = "1", features = ["sync", "macros", ]} +atomic_refcell = "0.1.8" +anyhow = "1.0" +tracing = { version = "0.1", features = ["log"] } + +[dev-dependencies] +rand = "0.8.5" +futures = "0.3.15" diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs b/frontend/rust-lib/flowy-task/src/lib.rs similarity index 90% rename from frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs rename to frontend/rust-lib/flowy-task/src/lib.rs index e91691cbd7..551f0e0293 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/mod.rs +++ b/frontend/rust-lib/flowy-task/src/lib.rs @@ -1,5 +1,4 @@ mod queue; -mod runner; mod scheduler; mod store; mod task; diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs b/frontend/rust-lib/flowy-task/src/queue.rs similarity index 86% rename from frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs rename to frontend/rust-lib/flowy-task/src/queue.rs index a20fdab7c6..b87d6b5734 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/queue.rs +++ b/frontend/rust-lib/flowy-task/src/queue.rs @@ -1,6 +1,5 @@ -use crate::services::tasks::task::{PendingTask, Task, TaskContent, TaskType}; +use crate::{PendingTask, Task}; use atomic_refcell::AtomicRefCell; - use std::cmp::Ordering; use std::collections::hash_map::Entry; use std::collections::{BinaryHeap, HashMap}; @@ -8,30 +7,25 @@ use std::ops::{Deref, DerefMut}; use std::sync::Arc; #[derive(Default)] -pub(crate) struct GridTaskQueue { +pub(crate) struct TaskQueue { // index_tasks for quick access index_tasks: HashMap>>, queue: BinaryHeap>>, } -impl GridTaskQueue { +impl TaskQueue { pub(crate) fn new() -> Self { Self::default() } pub(crate) fn push(&mut self, task: &Task) { if task.content.is_none() { - tracing::warn!("Ignore task: {} with empty content", task.id); + tracing::warn!("The task:{} with empty content will be not executed", task.id); return; } - let task_type = match task.content.as_ref().unwrap() { - TaskContent::Snapshot => TaskType::Snapshot, - TaskContent::Group => TaskType::Group, - TaskContent::Filter { .. } => TaskType::Filter, - }; let pending_task = PendingTask { - ty: task_type, + qos: task.qos, id: task.id, }; match self.index_tasks.entry(task.handler_id.clone()) { diff --git a/frontend/rust-lib/flowy-task/src/scheduler.rs b/frontend/rust-lib/flowy-task/src/scheduler.rs new file mode 100644 index 0000000000..b9f8e65042 --- /dev/null +++ b/frontend/rust-lib/flowy-task/src/scheduler.rs @@ -0,0 +1,187 @@ +use crate::queue::TaskQueue; +use crate::store::TaskStore; +use crate::{Task, TaskContent, TaskId, TaskState}; +use anyhow::Error; +use lib_infra::future::BoxResultFuture; +use lib_infra::ref_map::{RefCountHashMap, RefCountValue}; +use std::sync::Arc; +use std::time::Duration; + +use tokio::sync::{watch, RwLock}; +use tokio::time::interval; + +pub struct TaskDispatcher { + queue: TaskQueue, + store: TaskStore, + timeout: Duration, + handlers: RefCountHashMap, + + notifier: watch::Sender, + pub(crate) notifier_rx: Option>, +} + +impl TaskDispatcher { + pub fn new(timeout: Duration) -> Self { + let (notifier, notifier_rx) = watch::channel(false); + Self { + queue: TaskQueue::new(), + store: TaskStore::new(), + timeout, + handlers: RefCountHashMap::new(), + notifier, + notifier_rx: Some(notifier_rx), + } + } + + pub fn register_handler(&mut self, handler: T) + where + T: TaskHandler, + { + let handler_id = handler.handler_id().to_owned(); + self.handlers.insert(handler_id, RefCountTaskHandler(Arc::new(handler))); + } + + pub fn unregister_handler>(&mut self, handler_id: T) { + self.handlers.remove(handler_id.as_ref()); + } + + pub fn stop(&mut self) { + let _ = self.notifier.send(true); + self.queue.clear(); + self.store.clear(); + } + + pub(crate) async fn process_next_task(&mut self) -> Option<()> { + let pending_task = self.queue.mut_head(|list| list.pop())?; + let mut task = self.store.remove_task(&pending_task.id)?; + let ret = task.ret.take()?; + + // Do not execute the task if the task was cancelled. + if task.state().is_cancel() { + let _ = ret.send(task.into()); + self.notify(); + return None; + } + + let content = task.content.take()?; + if let Some(handler) = self.handlers.get(&task.handler_id) { + task.set_state(TaskState::Processing); + match tokio::time::timeout(self.timeout, handler.run(content)).await { + Ok(result) => match result { + Ok(_) => task.set_state(TaskState::Done), + Err(e) => { + tracing::error!("Process task failed: {:?}", e); + task.set_state(TaskState::Failure); + } + }, + Err(e) => { + tracing::error!("Process task timeout: {:?}", e); + task.set_state(TaskState::Timeout); + } + } + } else { + task.set_state(TaskState::Cancel); + } + let _ = ret.send(task.into()); + self.notify(); + None + } + + pub fn add_task(&mut self, task: Task) { + debug_assert!(!task.state().is_done()); + if task.state().is_done() { + return; + } + + self.queue.push(&task); + self.store.insert_task(task); + self.notify(); + } + + pub fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.store.read_task(task_id) + } + + pub fn cancel_task(&mut self, task_id: TaskId) { + if let Some(task) = self.store.mut_task(&task_id) { + task.set_state(TaskState::Cancel); + } + } + + pub fn next_task_id(&self) -> TaskId { + self.store.next_task_id() + } + + pub(crate) fn notify(&self) { + let _ = self.notifier.send(false); + } +} +pub struct TaskRunner(); +impl TaskRunner { + pub async fn run(dispatcher: Arc>) { + dispatcher.read().await.notify(); + let debounce_duration = Duration::from_millis(300); + let mut notifier = dispatcher.write().await.notifier_rx.take().expect("Only take once"); + loop { + // stops the runner if the notifier was closed. + if notifier.changed().await.is_err() { + break; + } + + // stops the runner if the value of notifier is `true` + if *notifier.borrow() { + break; + } + + let mut interval = interval(debounce_duration); + interval.tick().await; + let _ = dispatcher.write().await.process_next_task().await; + } + } +} + +pub trait TaskHandler: Send + Sync + 'static { + fn handler_id(&self) -> &str; + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error>; +} + +impl TaskHandler for Box +where + T: TaskHandler, +{ + fn handler_id(&self) -> &str { + (**self).handler_id() + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } +} + +impl TaskHandler for Arc +where + T: TaskHandler, +{ + fn handler_id(&self) -> &str { + (**self).handler_id() + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + (**self).run(content) + } +} +#[derive(Clone)] +struct RefCountTaskHandler(Arc); + +impl RefCountValue for RefCountTaskHandler { + fn did_remove(&self) {} +} + +impl std::ops::Deref for RefCountTaskHandler { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs b/frontend/rust-lib/flowy-task/src/store.rs similarity index 73% rename from frontend/rust-lib/flowy-grid/src/services/tasks/store.rs rename to frontend/rust-lib/flowy-task/src/store.rs index 9f14889e4d..800b189f21 100644 --- a/frontend/rust-lib/flowy-grid/src/services/tasks/store.rs +++ b/frontend/rust-lib/flowy-task/src/store.rs @@ -1,16 +1,15 @@ -use crate::services::tasks::task::Task; -use crate::services::tasks::{TaskId, TaskStatus}; +use crate::{Task, TaskId, TaskState}; use std::collections::HashMap; use std::mem; use std::sync::atomic::AtomicU32; use std::sync::atomic::Ordering::SeqCst; -pub(crate) struct GridTaskStore { +pub(crate) struct TaskStore { tasks: HashMap, task_id_counter: AtomicU32, } -impl GridTaskStore { +impl TaskStore { pub fn new() -> Self { Self { tasks: HashMap::new(), @@ -26,13 +25,20 @@ impl GridTaskStore { self.tasks.remove(task_id) } - #[allow(dead_code)] + pub(crate) fn mut_task(&mut self, task_id: &TaskId) -> Option<&mut Task> { + self.tasks.get_mut(task_id) + } + + pub(crate) fn read_task(&self, task_id: &TaskId) -> Option<&Task> { + self.tasks.get(task_id) + } + pub(crate) fn clear(&mut self) { let tasks = mem::take(&mut self.tasks); tasks.into_values().for_each(|mut task| { if task.ret.is_some() { let ret = task.ret.take().unwrap(); - task.set_status(TaskStatus::Cancel); + task.set_state(TaskState::Cancel); let _ = ret.send(task.into()); } }); diff --git a/frontend/rust-lib/flowy-task/src/task.rs b/frontend/rust-lib/flowy-task/src/task.rs new file mode 100644 index 0000000000..02f2322898 --- /dev/null +++ b/frontend/rust-lib/flowy-task/src/task.rs @@ -0,0 +1,142 @@ +use crate::TaskHandlerId; +use std::cmp::Ordering; +use tokio::sync::oneshot::{Receiver, Sender}; + +#[derive(Eq, Debug, Clone, Copy)] +pub enum QualityOfService { + Background, + UserInteractive, +} + +impl PartialEq for QualityOfService { + fn eq(&self, other: &Self) -> bool { + matches!( + (self, other), + (Self::Background, Self::Background) | (Self::UserInteractive, Self::UserInteractive) + ) + } +} + +pub type TaskId = u32; + +#[derive(Eq, Debug, Clone, Copy)] +pub struct PendingTask { + pub qos: QualityOfService, + pub id: TaskId, +} + +impl PartialEq for PendingTask { + fn eq(&self, other: &Self) -> bool { + self.id.eq(&other.id) + } +} + +impl PartialOrd for PendingTask { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PendingTask { + fn cmp(&self, other: &Self) -> Ordering { + match (self.qos, other.qos) { + // User interactive + (QualityOfService::UserInteractive, QualityOfService::UserInteractive) => self.id.cmp(&other.id), + (QualityOfService::UserInteractive, _) => Ordering::Greater, + (_, QualityOfService::UserInteractive) => Ordering::Less, + // background + (QualityOfService::Background, QualityOfService::Background) => self.id.cmp(&other.id), + } + } +} + +pub enum TaskContent { + Text(String), + Blob(Vec), +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum TaskState { + Pending, + Processing, + Done, + Failure, + Cancel, + Timeout, +} + +impl TaskState { + pub fn is_pending(&self) -> bool { + matches!(self, TaskState::Pending) + } + pub fn is_done(&self) -> bool { + matches!(self, TaskState::Done) + } + pub fn is_cancel(&self) -> bool { + matches!(self, TaskState::Cancel) + } + + pub fn is_processing(&self) -> bool { + matches!(self, TaskState::Processing) + } + + pub fn is_failed(&self) -> bool { + matches!(self, TaskState::Failure) + } +} + +pub struct Task { + pub id: TaskId, + pub handler_id: TaskHandlerId, + pub content: Option, + pub qos: QualityOfService, + state: TaskState, + pub ret: Option>, + pub recv: Option>, +} + +impl Task { + pub fn background(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::Background) + } + + pub fn user_interactive(handler_id: &str, id: TaskId, content: TaskContent) -> Self { + Self::new(handler_id, id, content, QualityOfService::UserInteractive) + } + + pub fn new(handler_id: &str, id: TaskId, content: TaskContent, qos: QualityOfService) -> Self { + let handler_id = handler_id.to_owned(); + let (ret, recv) = tokio::sync::oneshot::channel(); + Self { + handler_id, + id, + content: Some(content), + qos, + ret: Some(ret), + recv: Some(recv), + state: TaskState::Pending, + } + } + + pub fn state(&self) -> &TaskState { + &self.state + } + + pub(crate) fn set_state(&mut self, status: TaskState) { + self.state = status; + } +} + +pub struct TaskResult { + pub id: TaskId, + pub state: TaskState, +} + +impl std::convert::From for TaskResult { + fn from(task: Task) -> Self { + TaskResult { + id: task.id, + state: task.state().clone(), + } + } +} diff --git a/frontend/rust-lib/flowy-task/tests/main.rs b/frontend/rust-lib/flowy-task/tests/main.rs new file mode 100644 index 0000000000..d1f580aa65 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/main.rs @@ -0,0 +1 @@ +mod task_test; diff --git a/frontend/rust-lib/flowy-task/tests/task_test/mod.rs b/frontend/rust-lib/flowy-task/tests/task_test/mod.rs new file mode 100644 index 0000000000..864b94c3b7 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/mod.rs @@ -0,0 +1,3 @@ +mod script; +mod task_cancel_test; +mod task_order_test; diff --git a/frontend/rust-lib/flowy-task/tests/task_test/script.rs b/frontend/rust-lib/flowy-task/tests/task_test/script.rs new file mode 100644 index 0000000000..ef28315193 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/script.rs @@ -0,0 +1,196 @@ +use anyhow::Error; +use flowy_task::{Task, TaskContent, TaskDispatcher, TaskHandler, TaskId, TaskResult, TaskRunner, TaskState}; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use lib_infra::future::BoxResultFuture; +use lib_infra::ref_map::RefCountValue; +use rand::Rng; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::oneshot::Receiver; +use tokio::sync::RwLock; + +pub enum SearchScript { + AddTask { + task: Task, + }, + AddTasks { + tasks: Vec, + }, + #[allow(dead_code)] + Wait { + millisecond: u64, + }, + CancelTask { + task_id: TaskId, + }, + UnregisterHandler { + handler_id: String, + }, + AssertTaskStatus { + task_id: TaskId, + expected_status: TaskState, + }, + AssertExecuteOrder { + execute_order: Vec, + rets: Vec>, + }, +} + +pub struct SearchTest { + scheduler: Arc>, +} + +impl SearchTest { + pub async fn new() -> Self { + let duration = Duration::from_millis(1000); + let mut scheduler = TaskDispatcher::new(duration); + scheduler.register_handler(Arc::new(MockTextTaskHandler())); + scheduler.register_handler(Arc::new(MockBlobTaskHandler())); + scheduler.register_handler(Arc::new(MockTimeoutTaskHandler())); + + let scheduler = Arc::new(RwLock::new(scheduler)); + tokio::spawn(TaskRunner::run(scheduler.clone())); + + Self { scheduler } + } + + pub async fn next_task_id(&self) -> TaskId { + self.scheduler.read().await.next_task_id() + } + + pub async fn run_scripts(&self, scripts: Vec) { + for script in scripts { + self.run_script(script).await; + } + } + + pub async fn run_script(&self, script: SearchScript) { + match script { + SearchScript::AddTask { task } => { + self.scheduler.write().await.add_task(task); + } + SearchScript::CancelTask { task_id } => { + self.scheduler.write().await.cancel_task(task_id); + } + SearchScript::AddTasks { tasks } => { + let mut scheduler = self.scheduler.write().await; + for task in tasks { + scheduler.add_task(task); + } + } + SearchScript::Wait { millisecond } => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } + SearchScript::UnregisterHandler { handler_id } => { + self.scheduler.write().await.unregister_handler(handler_id); + } + SearchScript::AssertTaskStatus { + task_id, + expected_status, + } => { + let status = self.scheduler.read().await.read_task(&task_id).unwrap().state().clone(); + assert_eq!(status, expected_status); + } + SearchScript::AssertExecuteOrder { execute_order, rets } => { + let mut futures = FuturesUnordered::new(); + for ret in rets { + futures.push(ret); + } + let mut orders = vec![]; + while let Some(Ok(result)) = futures.next().await { + orders.push(result.id); + assert!(result.state.is_done()); + } + assert_eq!(execute_order, orders); + } + } + } +} + +pub struct MockTextTaskHandler(); +impl RefCountValue for MockTextTaskHandler { + fn did_remove(&self) {} +} + +impl TaskHandler for MockTextTaskHandler { + fn handler_id(&self) -> &str { + "1" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + let mut rng = rand::thread_rng(); + let millisecond = rng.gen_range(1..50); + Box::pin(async move { + match content { + TaskContent::Text(_s) => { + tokio::time::sleep(Duration::from_millis(millisecond)).await; + } + TaskContent::Blob(_) => panic!("Only support text"), + } + Ok(()) + }) + } +} + +pub fn make_text_background_task(task_id: TaskId, s: &str) -> (Task, Receiver) { + let mut task = Task::background("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) +} + +pub fn make_text_user_interactive_task(task_id: TaskId, s: &str) -> (Task, Receiver) { + let mut task = Task::user_interactive("1", task_id, TaskContent::Text(s.to_owned())); + let recv = task.recv.take().unwrap(); + (task, recv) +} + +pub struct MockBlobTaskHandler(); +impl RefCountValue for MockBlobTaskHandler { + fn did_remove(&self) {} +} + +impl TaskHandler for MockBlobTaskHandler { + fn handler_id(&self) -> &str { + "2" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(bytes) => { + let _msg = String::from_utf8(bytes).unwrap(); + tokio::time::sleep(Duration::from_millis(20)).await; + } + } + Ok(()) + }) + } +} + +pub struct MockTimeoutTaskHandler(); + +impl TaskHandler for MockTimeoutTaskHandler { + fn handler_id(&self) -> &str { + "3" + } + + fn run(&self, content: TaskContent) -> BoxResultFuture<(), Error> { + Box::pin(async move { + match content { + TaskContent::Text(_) => panic!("Only support blob"), + TaskContent::Blob(_bytes) => { + tokio::time::sleep(Duration::from_millis(2000)).await; + } + } + Ok(()) + }) + } +} + +pub fn make_timeout_task(task_id: TaskId) -> (Task, Receiver) { + let mut task = Task::background("3", task_id, TaskContent::Blob(vec![])); + let recv = task.recv.take().unwrap(); + (task, recv) +} diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs new file mode 100644 index 0000000000..f4a9422d86 --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_cancel_test.rs @@ -0,0 +1,88 @@ +use crate::task_test::script::SearchScript::*; +use crate::task_test::script::{make_text_background_task, make_timeout_task, SearchTest}; +use flowy_task::{QualityOfService, Task, TaskContent, TaskState}; + +#[tokio::test] +async fn task_cancel_background_task_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, "Hello world"); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AssertTaskStatus { + task_id: 1, + expected_status: TaskState::Pending, + }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Pending, + }, + CancelTask { task_id: 2 }, + AssertTaskStatus { + task_id: 2, + expected_status: TaskState::Cancel, + }, + ]) + .await; + + let result = ret_1.await.unwrap(); + assert_eq!(result.state, TaskState::Done); + + let result = ret_2.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_with_empty_handler_id_test() { + let test = SearchTest::new().await; + let mut task = Task::new( + "", + test.next_task_id().await, + TaskContent::Text("".to_owned()), + QualityOfService::Background, + ); + let ret = task.recv.take().unwrap(); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_can_not_find_handler_test() { + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, "Hello world"); + let handler_id = task.handler_id.clone(); + test.run_scripts(vec![UnregisterHandler { handler_id }, AddTask { task }]) + .await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Cancel); +} + +#[tokio::test] +async fn task_can_not_find_handler_test2() { + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; + let handler_id = "1".to_owned(); + for _i in 1..10000 { + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + tasks.push(task); + rets.push(ret); + } + + test.run_scripts(vec![UnregisterHandler { handler_id }, AddTasks { tasks }]) + .await; +} + +#[tokio::test] +async fn task_run_timeout_test() { + let test = SearchTest::new().await; + let (task, ret) = make_timeout_task(test.next_task_id().await); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert_eq!(result.state, TaskState::Timeout); +} diff --git a/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs new file mode 100644 index 0000000000..a2c084a42c --- /dev/null +++ b/frontend/rust-lib/flowy-task/tests/task_test/task_order_test.rs @@ -0,0 +1,111 @@ +use crate::task_test::script::{ + make_text_background_task, make_text_user_interactive_task, SearchScript::*, SearchTest, +}; + +#[tokio::test] +async fn task_add_single_background_task_test() { + let test = SearchTest::new().await; + let (task, ret) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![AddTask { task }]).await; + + let result = ret.await.unwrap(); + assert!(result.state.is_done()) +} + +#[tokio::test] +async fn task_add_multiple_background_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_background_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} + +#[tokio::test] +async fn task_add_multiple_user_interactive_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_user_interactive_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![3, 2, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} +#[tokio::test] +async fn task_add_multiple_different_kind_tasks_test() { + let test = SearchTest::new().await; + let (task_1, ret_1) = make_text_background_task(test.next_task_id().await, ""); + let (task_2, ret_2) = make_text_user_interactive_task(test.next_task_id().await, ""); + let (task_3, ret_3) = make_text_background_task(test.next_task_id().await, ""); + test.run_scripts(vec![ + AddTask { task: task_1 }, + AddTask { task: task_2 }, + AddTask { task: task_3 }, + AssertExecuteOrder { + execute_order: vec![2, 3, 1], + rets: vec![ret_1, ret_2, ret_3], + }, + ]) + .await; +} + +#[tokio::test] +async fn task_add_multiple_different_kind_tasks_test2() { + let test = SearchTest::new().await; + let mut tasks = vec![]; + let mut rets = vec![]; + + for i in 0..10 { + let (task, ret) = if i % 2 == 0 { + make_text_background_task(test.next_task_id().await, "") + } else { + make_text_user_interactive_task(test.next_task_id().await, "") + }; + tasks.push(task); + rets.push(ret); + } + + test.run_scripts(vec![ + AddTasks { tasks }, + AssertExecuteOrder { + execute_order: vec![10, 8, 6, 4, 2, 9, 7, 5, 3, 1], + rets, + }, + ]) + .await; +} + +// #[tokio::test] +// async fn task_add_1000_tasks_test() { +// let test = SearchTest::new().await; +// let mut tasks = vec![]; +// let mut execute_order = vec![]; +// let mut rets = vec![]; +// +// for i in 1..1000 { +// let (task, ret) = make_text_background_task(test.next_task_id().await, ""); +// execute_order.push(i); +// tasks.push(task); +// rets.push(ret); +// } +// execute_order.reverse(); +// +// test.run_scripts(vec![AddTasks { tasks }, AssertExecuteOrder { execute_order, rets }]) +// .await; +// } diff --git a/frontend/rust-lib/flowy-test/Cargo.toml b/frontend/rust-lib/flowy-test/Cargo.toml index 1ce06ff15b..b8d7b92a70 100644 --- a/frontend/rust-lib/flowy-test/Cargo.toml +++ b/frontend/rust-lib/flowy-test/Cargo.toml @@ -10,6 +10,7 @@ flowy-sdk = { path = "../flowy-sdk", default-features = false } flowy-user = { path = "../flowy-user"} flowy-net = { path = "../flowy-net"} flowy-folder = { path = "../flowy-folder", default-features = false} +flowy-document= { path = "../flowy-document", default-features = false} lib-dispatch = { path = "../lib-dispatch" } flowy-sync = { path = "../../../shared-lib/flowy-sync" } diff --git a/frontend/rust-lib/flowy-test/src/helper.rs b/frontend/rust-lib/flowy-test/src/helper.rs index ef530b3888..3d9a9d06ac 100644 --- a/frontend/rust-lib/flowy-test/src/helper.rs +++ b/frontend/rust-lib/flowy-test/src/helper.rs @@ -25,11 +25,16 @@ pub struct ViewTest { impl ViewTest { #[allow(dead_code)] - pub async fn new(sdk: &FlowySDKTest, data_type: ViewDataTypePB, layout: ViewLayoutTypePB, data: Vec) -> Self { + pub async fn new( + sdk: &FlowySDKTest, + data_format: ViewDataFormatPB, + layout: ViewLayoutTypePB, + data: Vec, + ) -> Self { let workspace = create_workspace(sdk, "Workspace", "").await; open_workspace(sdk, &workspace.id).await; let app = create_app(sdk, "App", "AppFlowy GitHub Project", &workspace.id).await; - let view = create_view(sdk, &app.id, data_type, layout, data).await; + let view = create_view(sdk, &app.id, data_format, layout, data).await; Self { sdk: sdk.clone(), workspace, @@ -39,15 +44,19 @@ impl ViewTest { } pub async fn new_grid_view(sdk: &FlowySDKTest, data: Vec) -> Self { - Self::new(sdk, ViewDataTypePB::Database, ViewLayoutTypePB::Grid, data).await + Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Grid, data).await } pub async fn new_board_view(sdk: &FlowySDKTest, data: Vec) -> Self { - Self::new(sdk, ViewDataTypePB::Database, ViewLayoutTypePB::Board, data).await + Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Board, data).await } - pub async fn new_text_block_view(sdk: &FlowySDKTest) -> Self { - Self::new(sdk, ViewDataTypePB::Text, ViewLayoutTypePB::Document, vec![]).await + pub async fn new_document_view(sdk: &FlowySDKTest) -> Self { + let view_data_format = match sdk.document_version() { + DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat, + DocumentVersionPB::V1 => ViewDataFormatPB::TreeFormat, + }; + Self::new(sdk, view_data_format, ViewLayoutTypePB::Document, vec![]).await } } @@ -97,7 +106,7 @@ async fn create_app(sdk: &FlowySDKTest, name: &str, desc: &str, workspace_id: &s async fn create_view( sdk: &FlowySDKTest, app_id: &str, - data_type: ViewDataTypePB, + data_format: ViewDataFormatPB, layout: ViewLayoutTypePB, data: Vec, ) -> ViewPB { @@ -106,7 +115,7 @@ async fn create_view( name: "View A".to_string(), desc: "".to_string(), thumbnail: Some("http://1.png".to_string()), - data_type, + data_format, layout, view_content_data: data, }; diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index 8f6aacb44d..68e9834abf 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/frontend/rust-lib/flowy-test/src/lib.rs @@ -2,7 +2,9 @@ pub mod event_builder; pub mod helper; use crate::helper::*; -use flowy_net::{get_client_server_configuration, ClientServerConfiguration}; + +use flowy_document::entities::DocumentVersionPB; +use flowy_net::get_client_server_configuration; use flowy_sdk::{FlowySDK, FlowySDKConfig}; use flowy_user::entities::UserProfilePB; use nanoid::nanoid; @@ -27,16 +29,16 @@ impl std::ops::Deref for FlowySDKTest { impl std::default::Default for FlowySDKTest { fn default() -> Self { - let server_config = get_client_server_configuration().unwrap(); - let sdk = Self::new(server_config); - std::mem::forget(sdk.dispatcher()); - sdk + Self::new(DocumentVersionPB::V0) } } impl FlowySDKTest { - pub fn new(server_config: ClientServerConfiguration) -> Self { - let config = FlowySDKConfig::new(&root_dir(), server_config, &nanoid!(6)).log_filter("info"); + pub fn new(document_version: DocumentVersionPB) -> Self { + let server_config = get_client_server_configuration().unwrap(); + let config = FlowySDKConfig::new(&root_dir(), &nanoid!(6), server_config) + .with_document_version(document_version) + .log_filter("info"); let sdk = std::thread::spawn(|| FlowySDK::new(config)).join().unwrap(); std::mem::forget(sdk.dispatcher()); Self { inner: sdk } @@ -52,4 +54,8 @@ impl FlowySDKTest { init_user_setting(self.inner.dispatcher()).await; context.user_profile } + + pub fn document_version(&self) -> DocumentVersionPB { + self.inner.config.document.version.clone() + } } diff --git a/frontend/rust-lib/flowy-user/Cargo.toml b/frontend/rust-lib/flowy-user/Cargo.toml index 46490817ce..d2c0d55f32 100644 --- a/frontend/rust-lib/flowy-user/Cargo.toml +++ b/frontend/rust-lib/flowy-user/Cargo.toml @@ -24,7 +24,7 @@ lazy_static = "1.4.0" diesel = {version = "1.4.8", features = ["sqlite"]} diesel_derives = {version = "1.4.1", features = ["sqlite"]} once_cell = "1.7.2" -parking_lot = "0.11" +parking_lot = "0.12.1" strum = "0.21" strum_macros = "0.21" tokio = { version = "1", features = ["rt"] } @@ -48,4 +48,4 @@ rand = "0.8.5" dart = ["lib-infra/dart"] [build-dependencies] -lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen"] } \ No newline at end of file +lib-infra = { path = "../../../shared-lib/lib-infra", features = ["proto_gen"] } \ No newline at end of file diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs index 4b423db3af..f77be6880c 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs @@ -12,6 +12,12 @@ pub struct UserTokenPB { pub token: String, } +#[derive(ProtoBuf, Default, Clone)] +pub struct UserSettingPB { + #[pb(index = 1)] + pub(crate) user_folder: String, +} + #[derive(ProtoBuf, Default, Debug, PartialEq, Eq, Clone)] pub struct UserProfilePB { #[pb(index = 1)] diff --git a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs index 1d4e77896b..d9364ddad8 100644 --- a/frontend/rust-lib/flowy-user/src/entities/user_setting.rs +++ b/frontend/rust-lib/flowy-user/src/entities/user_setting.rs @@ -17,14 +17,20 @@ pub struct AppearanceSettingsPB { pub theme: String, #[pb(index = 2)] + pub font: String, + + #[pb(index = 3)] + pub monospace_font: String, + + #[pb(index = 4)] #[serde(default)] pub locale: LocaleSettingsPB, - #[pb(index = 3)] + #[pb(index = 5)] #[serde(default = "DEFAULT_RESET_VALUE")] pub reset_to_default: bool, - #[pb(index = 4)] + #[pb(index = 6)] #[serde(default)] pub setting_key_value: HashMap, } @@ -50,12 +56,16 @@ impl std::default::Default for LocaleSettingsPB { } pub const APPEARANCE_DEFAULT_THEME: &str = "light"; +pub const APPEARANCE_DEFAULT_FONT: &str = "Poppins"; +pub const APPEARANCE_DEFAULT_MONOSPACE_FONT: &str = "SF Mono"; const APPEARANCE_RESET_AS_DEFAULT: bool = true; impl std::default::Default for AppearanceSettingsPB { fn default() -> Self { AppearanceSettingsPB { theme: APPEARANCE_DEFAULT_THEME.to_owned(), + font: APPEARANCE_DEFAULT_FONT.to_owned(), + monospace_font: APPEARANCE_DEFAULT_MONOSPACE_FONT.to_owned(), locale: LocaleSettingsPB::default(), reset_to_default: APPEARANCE_RESET_AS_DEFAULT, setting_key_value: HashMap::default(), diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index ced1ede179..a6a6d69f5c 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -19,6 +19,7 @@ pub fn create(user_session: Arc) -> Module { .event(UserEvent::CheckUser, check_user_handler) .event(UserEvent::SetAppearanceSetting, set_appearance_setting) .event(UserEvent::GetAppearanceSetting, get_appearance_setting) + .event(UserEvent::GetUserSetting, get_user_setting) } pub trait UserCloudService: Send + Sync { @@ -62,4 +63,7 @@ pub enum UserEvent { #[event(output = "AppearanceSettingsPB")] GetAppearanceSetting = 8, + + #[event(output = "UserSettingPB")] + GetUserSetting = 9, } diff --git a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs index 8a10dd89e2..1abc30e2c6 100644 --- a/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs +++ b/frontend/rust-lib/flowy-user/src/handlers/user_handler.rs @@ -1,5 +1,6 @@ use crate::entities::{ - AppearanceSettingsPB, UpdateUserProfileParams, UpdateUserProfilePayloadPB, UserProfilePB, APPEARANCE_DEFAULT_THEME, + AppearanceSettingsPB, UpdateUserProfileParams, UpdateUserProfilePayloadPB, UserProfilePB, UserSettingPB, + APPEARANCE_DEFAULT_THEME, }; use crate::{errors::FlowyError, services::UserSession}; use flowy_database::kv::KV; @@ -54,7 +55,7 @@ pub async fn set_appearance_setting(data: Data) -> Result< Ok(()) } -#[tracing::instrument(err)] +#[tracing::instrument(level = "debug", err)] pub async fn get_appearance_setting() -> DataResult { match KV::get_str(APPEARANCE_SETTING_CACHE_KEY) { None => data_result(AppearanceSettingsPB::default()), @@ -70,3 +71,9 @@ pub async fn get_appearance_setting() -> DataResult>) -> DataResult { + let user_setting = session.user_setting()?; + data_result(user_setting) +} diff --git a/frontend/rust-lib/flowy-user/src/services/user_session.rs b/frontend/rust-lib/flowy-user/src/services/user_session.rs index 3822a8a0db..66ae641986 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_session.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_session.rs @@ -1,5 +1,5 @@ use crate::entities::{ - SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, + SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfilePB, UserSettingPB, }; use crate::{ dart_notification::*, @@ -169,6 +169,13 @@ impl UserSession { Ok(format!("{}/{}", self.config.root_dir, session.user_id)) } + pub fn user_setting(&self) -> Result { + let user_setting = UserSettingPB { + user_folder: self.user_dir()?, + }; + Ok(user_setting) + } + pub fn user_id(&self) -> Result { Ok(self.get_session()?.user_id) } diff --git a/frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg b/frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg new file mode 100644 index 0000000000..57e29d8b6a Binary files /dev/null and b/frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg differ diff --git a/frontend/scripts/makefile/desktop.toml b/frontend/scripts/makefile/desktop.toml index b97770378b..f6c5cf3f80 100644 --- a/frontend/scripts/makefile/desktop.toml +++ b/frontend/scripts/makefile/desktop.toml @@ -164,19 +164,19 @@ script = [ ] script_runner = "@duckscript" -[tasks.test-lib-build] +[tasks.build-test-lib] category = "Build" dependencies = ["env_check"] -run_task = { name = ["setup-test-crate-type","test-sdk-build", "copy-to-sandbox-folder", "restore-test-crate-type"] } +run_task = { name = ["setup-test-crate-type","build-test-backend", "copy-to-sandbox-folder", "restore-test-crate-type"] } -[tasks.test-sdk-build] +[tasks.build-test-backend] private = true script = [ """ cd rust-lib/ rustup show echo cargo build --package=dart-ffi --target ${TEST_COMPILE_TARGET} --features "${FEATURES}" - cargo build --package=dart-ffi --target ${TEST_COMPILE_TARGET} --features "${FEATURES}" + RUST_LOG=${RUST_LOG} cargo build --package=dart-ffi --target ${TEST_COMPILE_TARGET} --features "${FEATURES}" cd ../ """, ] diff --git a/frontend/scripts/makefile/tests.toml b/frontend/scripts/makefile/tests.toml index 02c680e71c..d8a3bd7089 100644 --- a/frontend/scripts/makefile/tests.toml +++ b/frontend/scripts/makefile/tests.toml @@ -1,4 +1,12 @@ +[tasks.dart_unit_test] +dependencies = ["build-test-lib"] +description = "Run flutter unit tests" +script = ''' +cd app_flowy +flutter test --dart-define=RUST_LOG=${TEST_RUST_LOG} --concurrency=1 +''' + [tasks.rust_unit_test] run_task = { name = ["rust_lib_unit_test", "shared_lib_unit_test"] } diff --git a/shared-lib/Cargo.lock b/shared-lib/Cargo.lock index e519a829cd..2a3a2a5f6a 100644 --- a/shared-lib/Cargo.lock +++ b/shared-lib/Cargo.lock @@ -404,39 +404,14 @@ dependencies = [ ] [[package]] -name = "flowy-folder-data-model" +name = "flowy-http-model" version = "0.1.0" dependencies = [ "bytes", - "chrono", - "derive_more", "flowy-derive", - "flowy-error-code", "lib-infra", - "log", - "nanoid", + "md5", "protobuf", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros", - "unicode-segmentation", -] - -[[package]] -name = "flowy-grid-data-model" -version = "0.1.0" -dependencies = [ - "bytes", - "flowy-error-code", - "indexmap", - "lib-infra", - "nanoid", - "serde", - "serde_json", - "serde_repr", - "tracing", ] [[package]] @@ -449,14 +424,14 @@ dependencies = [ "dashmap", "dissimilar", "flowy-derive", - "flowy-folder-data-model", - "flowy-grid-data-model", + "flowy-http-model", + "folder-rev-model", "futures", + "grid-rev-model", "lib-infra", "lib-ot", "log", - "md5", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "protobuf", "serde", "serde_json", @@ -473,6 +448,20 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "folder-rev-model" +version = "0.1.0" +dependencies = [ + "bytes", + "chrono", + "nanoid", + "serde", + "serde_json", + "serde_repr", + "strum", + "strum_macros", +] + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -648,6 +637,19 @@ dependencies = [ "walkdir", ] +[[package]] +name = "grid-rev-model" +version = "0.1.0" +dependencies = [ + "bytes", + "flowy-error-code", + "indexmap", + "nanoid", + "serde", + "serde_json", + "serde_repr", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -815,6 +817,7 @@ dependencies = [ "bytes", "dashmap", "derive_more", + "indexmap", "indextree", "lazy_static", "log", @@ -843,7 +846,7 @@ dependencies = [ "futures-util", "lib-infra", "log", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "paste", "pin-project", "protobuf", diff --git a/shared-lib/Cargo.toml b/shared-lib/Cargo.toml index 1ddded168d..070cf36ba7 100644 --- a/shared-lib/Cargo.toml +++ b/shared-lib/Cargo.toml @@ -1,14 +1,15 @@ [workspace] members = [ - "flowy-folder-data-model", + "folder-rev-model", "flowy-sync", + "flowy-http-model", "lib-ot", "lib-ws", "lib-infra", "flowy-derive", "flowy-ast", "flowy-error-code", - "flowy-grid-data-model", + "grid-rev-model", ] [profile.dev] diff --git a/shared-lib/flowy-ast/src/ast.rs b/shared-lib/flowy-ast/src/ast.rs index 4fec9a5542..806a86d0f1 100644 --- a/shared-lib/flowy-ast/src/ast.rs +++ b/shared-lib/flowy-ast/src/ast.rs @@ -1,3 +1,6 @@ +#![allow(clippy::all)] +#![allow(unused_attributes)] +#![allow(unused_assignments)] use crate::{attr, ty_ext::*, AttrsContainer, Ctxt}; use syn::{self, punctuated::Punctuated}; diff --git a/shared-lib/flowy-error-code/Cargo.toml b/shared-lib/flowy-error-code/Cargo.toml index 4da6b7c0a1..ab72ad2711 100644 --- a/shared-lib/flowy-error-code/Cargo.toml +++ b/shared-lib/flowy-error-code/Cargo.toml @@ -11,7 +11,7 @@ protobuf = {version = "2.18.0"} derive_more = {version = "0.99", features = ["display"]} [build-dependencies] -lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] } +lib-infra = { path = "../lib-infra", features = ["proto_gen"] } [features] dart = ["lib-infra/dart"] \ No newline at end of file diff --git a/shared-lib/flowy-error-code/src/code.rs b/shared-lib/flowy-error-code/src/code.rs index 35f5f572f2..c2c95caab1 100644 --- a/shared-lib/flowy-error-code/src/code.rs +++ b/shared-lib/flowy-error-code/src/code.rs @@ -126,6 +126,12 @@ pub enum ErrorCode { #[display(fmt = "Invalid data")] InvalidData = 1000, + #[display(fmt = "Serde")] + Serde = 1001, + + #[display(fmt = "Protobuf serde")] + ProtobufSerde = 1002, + #[display(fmt = "Out of bounds")] OutOfBounds = 10001, } diff --git a/shared-lib/flowy-folder-data-model/Cargo.toml b/shared-lib/flowy-folder-data-model/Cargo.toml deleted file mode 100644 index 15b8e47bac..0000000000 --- a/shared-lib/flowy-folder-data-model/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "flowy-folder-data-model" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -flowy-derive = { path = "../flowy-derive" } -protobuf = {version = "2.18.0"} -bytes = "1.0" -unicode-segmentation = "1.8" -strum = "0.21" -strum_macros = "0.21" -derive_more = {version = "0.99", features = ["display"]} -log = "0.4.14" -nanoid = "0.4.0" -chrono = { version = "0.4" } -flowy-error-code = { path = "../flowy-error-code"} -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0" -serde_repr = "0.1" - -[build-dependencies] -lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] } - -[features] -default = [] -backend = [] -frontend = [] -dart = ["lib-infra/dart", "flowy-error-code/dart"] \ No newline at end of file diff --git a/shared-lib/flowy-folder-data-model/src/lib.rs b/shared-lib/flowy-folder-data-model/src/lib.rs deleted file mode 100644 index 783eab5f75..0000000000 --- a/shared-lib/flowy-folder-data-model/src/lib.rs +++ /dev/null @@ -1,9 +0,0 @@ -#[macro_use] -mod macros; - -pub mod revision; -pub mod user_default; - -pub mod errors { - pub use flowy_error_code::ErrorCode; -} diff --git a/shared-lib/flowy-grid-data-model/src/lib.rs b/shared-lib/flowy-grid-data-model/src/lib.rs deleted file mode 100644 index c4306291e8..0000000000 --- a/shared-lib/flowy-grid-data-model/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod parser; -pub mod revision; diff --git a/shared-lib/flowy-grid-data-model/src/parser/mod.rs b/shared-lib/flowy-grid-data-model/src/parser/mod.rs deleted file mode 100644 index 8a9739e5b3..0000000000 --- a/shared-lib/flowy-grid-data-model/src/parser/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod str_parser; -pub use str_parser::*; diff --git a/shared-lib/flowy-grid-data-model/tests/serde_test.rs b/shared-lib/flowy-grid-data-model/tests/serde_test.rs deleted file mode 100644 index b544e10588..0000000000 --- a/shared-lib/flowy-grid-data-model/tests/serde_test.rs +++ /dev/null @@ -1,10 +0,0 @@ -use flowy_grid_data_model::revision::*; - -#[test] -fn grid_default_serde_test() { - let grid_id = "1".to_owned(); - let grid = GridRevision::new(&grid_id); - - let json = serde_json::to_string(&grid).unwrap(); - assert_eq!(json, r#"{"grid_id":"1","fields":[],"blocks":[]}"#) -} diff --git a/shared-lib/flowy-http-model/Cargo.toml b/shared-lib/flowy-http-model/Cargo.toml new file mode 100644 index 0000000000..2b81acef41 --- /dev/null +++ b/shared-lib/flowy-http-model/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "flowy-http-model" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1.0" +lib-infra = { path = "../lib-infra" } +flowy-derive = { path = "../flowy-derive" } +protobuf = {version = "2.18.0"} +md5 = "0.7.0" + +[build-dependencies] +lib-infra = { path = "../lib-infra", features = ["proto_gen"] } + diff --git a/shared-lib/flowy-sync/Flowy.toml b/shared-lib/flowy-http-model/Flowy.toml similarity index 100% rename from shared-lib/flowy-sync/Flowy.toml rename to shared-lib/flowy-http-model/Flowy.toml diff --git a/shared-lib/flowy-sync/build.rs b/shared-lib/flowy-http-model/build.rs similarity index 100% rename from shared-lib/flowy-sync/build.rs rename to shared-lib/flowy-http-model/build.rs diff --git a/shared-lib/flowy-sync/src/entities/document.rs b/shared-lib/flowy-http-model/src/entities/document.rs similarity index 64% rename from shared-lib/flowy-sync/src/entities/document.rs rename to shared-lib/flowy-http-model/src/entities/document.rs index f351d95677..da19023887 100644 --- a/shared-lib/flowy-sync/src/entities/document.rs +++ b/shared-lib/flowy-http-model/src/entities/document.rs @@ -1,9 +1,5 @@ -use crate::{ - entities::revision::{RepeatedRevision, Revision}, - errors::CollaborateError, -}; +use crate::entities::revision::{RepeatedRevision, Revision}; use flowy_derive::ProtoBuf; -use lib_ot::text_delta::TextOperations; #[derive(ProtoBuf, Default, Debug, Clone)] pub struct CreateDocumentParams { @@ -20,7 +16,7 @@ pub struct DocumentPayloadPB { pub doc_id: String, #[pb(index = 2)] - pub content: String, + pub data: Vec, #[pb(index = 3)] pub rev_id: i64, @@ -30,20 +26,16 @@ pub struct DocumentPayloadPB { } impl std::convert::TryFrom for DocumentPayloadPB { - type Error = CollaborateError; + type Error = String; fn try_from(revision: Revision) -> Result { if !revision.is_initial() { - return Err(CollaborateError::revision_conflict() - .context("Revision's rev_id should be 0 when creating the document")); + return Err("Revision's rev_id should be 0 when creating the document".to_string()); } - let delta = TextOperations::from_bytes(&revision.bytes)?; - let doc_json = delta.json_str(); - Ok(DocumentPayloadPB { doc_id: revision.object_id, - content: doc_json, + data: revision.bytes, rev_id: revision.rev_id, base_rev_id: revision.base_rev_id, }) @@ -59,27 +51,6 @@ pub struct ResetDocumentParams { pub revisions: RepeatedRevision, } -#[derive(ProtoBuf, Default, Debug, Clone)] -pub struct DocumentOperationsPB { - #[pb(index = 1)] - pub doc_id: String, - - #[pb(index = 2)] - pub operations_str: String, -} - -#[derive(ProtoBuf, Default, Debug, Clone)] -pub struct NewDocUserPB { - #[pb(index = 1)] - pub user_id: String, - - #[pb(index = 2)] - pub rev_id: i64, - - #[pb(index = 3)] - pub doc_id: String, -} - #[derive(ProtoBuf, Default, Debug, Clone)] pub struct DocumentIdPB { #[pb(index = 1)] diff --git a/shared-lib/flowy-sync/src/entities/folder.rs b/shared-lib/flowy-http-model/src/entities/folder.rs similarity index 100% rename from shared-lib/flowy-sync/src/entities/folder.rs rename to shared-lib/flowy-http-model/src/entities/folder.rs diff --git a/shared-lib/flowy-sync/src/entities/mod.rs b/shared-lib/flowy-http-model/src/entities/mod.rs similarity index 81% rename from shared-lib/flowy-sync/src/entities/mod.rs rename to shared-lib/flowy-http-model/src/entities/mod.rs index 178c8a0f35..649ee1bbaa 100644 --- a/shared-lib/flowy-sync/src/entities/mod.rs +++ b/shared-lib/flowy-http-model/src/entities/mod.rs @@ -1,5 +1,4 @@ pub mod document; pub mod folder; -pub mod parser; pub mod revision; pub mod ws_data; diff --git a/shared-lib/flowy-sync/src/entities/revision.rs b/shared-lib/flowy-http-model/src/entities/revision.rs similarity index 75% rename from shared-lib/flowy-sync/src/entities/revision.rs rename to shared-lib/flowy-http-model/src/entities/revision.rs index 4f7cc7a148..3c7e906792 100644 --- a/shared-lib/flowy-sync/src/entities/revision.rs +++ b/shared-lib/flowy-http-model/src/entities/revision.rs @@ -1,9 +1,8 @@ +use crate::util::md5; use bytes::Bytes; -use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; +use flowy_derive::ProtoBuf; use std::{convert::TryFrom, fmt::Formatter, ops::RangeInclusive}; -pub type RevisionObject = lib_ot::text_delta::TextOperations; - #[derive(PartialEq, Eq, Clone, Default, ProtoBuf)] pub struct Revision { #[pb(index = 1)] @@ -20,12 +19,6 @@ pub struct Revision { #[pb(index = 5)] pub object_id: String, - - #[pb(index = 6)] - ty: RevType, // Deprecated - - #[pb(index = 7)] - pub user_id: String, } impl std::convert::From> for Revision { @@ -36,25 +29,7 @@ impl std::convert::From> for Revision { } impl Revision { - pub fn is_empty(&self) -> bool { - self.base_rev_id == self.rev_id - } - - pub fn pair_rev_id(&self) -> (i64, i64) { - (self.base_rev_id, self.rev_id) - } - - pub fn is_initial(&self) -> bool { - self.rev_id == 0 - } - - pub fn initial_revision(user_id: &str, object_id: &str, bytes: Bytes) -> Self { - let md5 = md5(&bytes); - Self::new(object_id, 0, 0, bytes, user_id, md5) - } - - pub fn new(object_id: &str, base_rev_id: i64, rev_id: i64, bytes: Bytes, user_id: &str, md5: String) -> Revision { - let user_id = user_id.to_owned(); + pub fn new>(object_id: &str, base_rev_id: i64, rev_id: i64, bytes: Bytes, md5: T) -> Revision { let object_id = object_id.to_owned(); let bytes = bytes.to_vec(); let base_rev_id = base_rev_id; @@ -68,12 +43,27 @@ impl Revision { base_rev_id, rev_id, bytes, - md5, + md5: md5.into(), object_id, - ty: RevType::DeprecatedLocal, - user_id, } } + + pub fn is_empty(&self) -> bool { + self.base_rev_id == self.rev_id + } + + pub fn pair_rev_id(&self) -> (i64, i64) { + (self.base_rev_id, self.rev_id) + } + + pub fn is_initial(&self) -> bool { + self.rev_id == 0 + } + + pub fn initial_revision(object_id: &str, bytes: Bytes) -> Self { + let md5 = md5(&bytes); + Self::new(object_id, 0, 0, bytes, md5) + } } impl std::fmt::Debug for Revision { @@ -81,14 +71,6 @@ impl std::fmt::Debug for Revision { let _ = f.write_fmt(format_args!("object_id {}, ", self.object_id))?; let _ = f.write_fmt(format_args!("base_rev_id {}, ", self.base_rev_id))?; let _ = f.write_fmt(format_args!("rev_id {}, ", self.rev_id))?; - match RevisionObject::from_bytes(&self.bytes) { - Ok(object) => { - let _ = f.write_fmt(format_args!("object {:?}", object.json_str()))?; - } - Err(e) => { - let _ = f.write_fmt(format_args!("object {:?}", e))?; - } - } Ok(()) } } @@ -186,10 +168,10 @@ impl std::fmt::Display for RevisionRange { } impl RevisionRange { - pub fn len(&self) -> i64 { + pub fn len(&self) -> u64 { debug_assert!(self.end >= self.start); if self.end >= self.start { - self.end - self.start + 1 + (self.end - self.start + 1) as u64 } else { 0 } @@ -208,21 +190,3 @@ impl RevisionRange { self.iter().collect::>() } } - -#[inline] -pub fn md5>(data: T) -> String { - let md5 = format!("{:x}", md5::compute(data)); - md5 -} - -#[derive(Debug, ProtoBuf_Enum, Clone, Eq, PartialEq)] -pub enum RevType { - DeprecatedLocal = 0, - DeprecatedRemote = 1, -} - -impl std::default::Default for RevType { - fn default() -> Self { - RevType::DeprecatedLocal - } -} diff --git a/shared-lib/flowy-sync/src/entities/ws_data.rs b/shared-lib/flowy-http-model/src/entities/ws_data.rs similarity index 86% rename from shared-lib/flowy-sync/src/entities/ws_data.rs rename to shared-lib/flowy-http-model/src/entities/ws_data.rs index 8bcbe3615b..ee8841c325 100644 --- a/shared-lib/flowy-sync/src/entities/ws_data.rs +++ b/shared-lib/flowy-http-model/src/entities/ws_data.rs @@ -1,10 +1,7 @@ -use crate::{ - entities::revision::{RepeatedRevision, RevId, Revision, RevisionRange}, - errors::CollaborateError, -}; +use crate::entities::revision::{RepeatedRevision, RevId, Revision, RevisionRange}; use bytes::Bytes; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryInto; #[derive(Debug, Clone, ProtoBuf_Enum, Eq, PartialEq, Hash)] pub enum ClientRevisionWSDataType { @@ -12,16 +9,16 @@ pub enum ClientRevisionWSDataType { ClientPing = 1, } -impl ClientRevisionWSDataType { - pub fn data(&self, bytes: Bytes) -> Result - where - T: TryFrom, - { - T::try_from(bytes) - } -} +// impl ClientRevisionWSDataType { +// pub fn data(&self, bytes: Bytes) -> Result +// where +// T: TryFrom, +// { +// T::try_from(bytes) +// } +// } -impl std::default::Default for ClientRevisionWSDataType { +impl Default for ClientRevisionWSDataType { fn default() -> Self { ClientRevisionWSDataType::ClientPushRev } @@ -39,7 +36,7 @@ pub struct ClientRevisionWSData { pub revisions: RepeatedRevision, #[pb(index = 4)] - data_id: String, + pub data_id: String, } impl ClientRevisionWSData { @@ -79,7 +76,7 @@ pub enum ServerRevisionWSDataType { UserConnect = 3, } -impl std::default::Default for ServerRevisionWSDataType { +impl Default for ServerRevisionWSDataType { fn default() -> Self { ServerRevisionWSDataType::ServerPushRev } diff --git a/shared-lib/flowy-http-model/src/lib.rs b/shared-lib/flowy-http-model/src/lib.rs new file mode 100644 index 0000000000..7e2586293c --- /dev/null +++ b/shared-lib/flowy-http-model/src/lib.rs @@ -0,0 +1,6 @@ +pub mod util; + +pub mod protobuf; + +mod entities; +pub use entities::*; diff --git a/shared-lib/flowy-http-model/src/util.rs b/shared-lib/flowy-http-model/src/util.rs new file mode 100644 index 0000000000..00c4473e93 --- /dev/null +++ b/shared-lib/flowy-http-model/src/util.rs @@ -0,0 +1,5 @@ +#[inline] +pub fn md5>(data: T) -> String { + let md5 = format!("{:x}", md5::compute(data)); + md5 +} diff --git a/shared-lib/flowy-sync/Cargo.toml b/shared-lib/flowy-sync/Cargo.toml index 154054e9a8..888de04bd9 100644 --- a/shared-lib/flowy-sync/Cargo.toml +++ b/shared-lib/flowy-sync/Cargo.toml @@ -9,12 +9,12 @@ edition = "2018" lib-ot = { path = "../lib-ot" } lib-infra = { path = "../lib-infra" } flowy-derive = { path = "../flowy-derive" } -flowy-folder-data-model = { path = "../flowy-folder-data-model" } -flowy-grid-data-model = { path = "../flowy-grid-data-model" } +folder-rev-model = { path = "../folder-rev-model" } +grid-rev-model = { path = "../grid-rev-model" } +flowy-http-model= { path = "../flowy-http-model" } protobuf = {version = "2.18.0"} bytes = "1.0" log = "0.4.14" -md5 = "0.7.0" tokio = { version = "1", features = ["full"] } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = {version = "1.0"} @@ -24,13 +24,7 @@ url = "2.2" strum = "0.21" strum_macros = "0.21" chrono = "0.4.19" -parking_lot = "0.11" +parking_lot = "0.12.1" dashmap = "5" futures = "0.3.15" async-stream = "0.3.2" - -[build-dependencies] -lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] } - -[features] -dart = ["lib-infra/dart"] \ No newline at end of file diff --git a/shared-lib/flowy-sync/src/READ_ME.json b/shared-lib/flowy-sync/src/READ_ME.json deleted file mode 100644 index 624802720c..0000000000 --- a/shared-lib/flowy-sync/src/READ_ME.json +++ /dev/null @@ -1 +0,0 @@ -[{"insert":"\n👋 Welcome to AppFlowy!"},{"insert":"\n","attributes":{"header":1}},{"insert":"\nHere are the basics"},{"insert":"\n","attributes":{"header":2}},{"insert":"Click anywhere and just start typing"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Highlight","attributes":{"background":"#fff2cd"}},{"insert":" any text, and use the menu at the bottom to "},{"insert":"style","attributes":{"italic":true}},{"insert":" "},{"insert":"your","attributes":{"bold":true}},{"insert":" "},{"insert":"writing","attributes":{"underline":true}},{"insert":" "},{"insert":"however","attributes":{"code":true}},{"insert":" "},{"insert":"you","attributes":{"strike":true}},{"insert":" "},{"insert":"like","attributes":{"background":"#e8e0ff"}},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click "},{"insert":"+ New Page","attributes":{"background":"#defff1","bold":true}},{"insert":" button at the bottom of your sidebar to add a new page"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click the "},{"insert":"'","attributes":{"background":"#defff1"}},{"insert":"+","attributes":{"background":"#defff1","bold":true}},{"insert":"'","attributes":{"background":"#defff1"}},{"insert":" next to any page title in the sidebar to quickly add a new subpage"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\nHave a question? "},{"insert":"\n","attributes":{"header":2}},{"insert":"Click the "},{"insert":"'?'","attributes":{"background":"#defff1","bold":true}},{"insert":" at the bottom right for help and support.\n\nLike AppFlowy? Follow us:"},{"insert":"\n","attributes":{"header":2}},{"insert":"GitHub: https://github.com/AppFlowy-IO/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Twitter: https://twitter.com/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Newsletter: https://www.appflowy.io/blog"},{"insert":"\n","attributes":{"blockquote":true}}] \ No newline at end of file diff --git a/shared-lib/flowy-sync/src/client_document/default/READ_ME.json b/shared-lib/flowy-sync/src/client_document/default/READ_ME.json deleted file mode 100644 index 624802720c..0000000000 --- a/shared-lib/flowy-sync/src/client_document/default/READ_ME.json +++ /dev/null @@ -1 +0,0 @@ -[{"insert":"\n👋 Welcome to AppFlowy!"},{"insert":"\n","attributes":{"header":1}},{"insert":"\nHere are the basics"},{"insert":"\n","attributes":{"header":2}},{"insert":"Click anywhere and just start typing"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Highlight","attributes":{"background":"#fff2cd"}},{"insert":" any text, and use the menu at the bottom to "},{"insert":"style","attributes":{"italic":true}},{"insert":" "},{"insert":"your","attributes":{"bold":true}},{"insert":" "},{"insert":"writing","attributes":{"underline":true}},{"insert":" "},{"insert":"however","attributes":{"code":true}},{"insert":" "},{"insert":"you","attributes":{"strike":true}},{"insert":" "},{"insert":"like","attributes":{"background":"#e8e0ff"}},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click "},{"insert":"+ New Page","attributes":{"background":"#defff1","bold":true}},{"insert":" button at the bottom of your sidebar to add a new page"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"Click the "},{"insert":"'","attributes":{"background":"#defff1"}},{"insert":"+","attributes":{"background":"#defff1","bold":true}},{"insert":"'","attributes":{"background":"#defff1"}},{"insert":" next to any page title in the sidebar to quickly add a new subpage"},{"insert":"\n","attributes":{"list":"unchecked"}},{"insert":"\nHave a question? "},{"insert":"\n","attributes":{"header":2}},{"insert":"Click the "},{"insert":"'?'","attributes":{"background":"#defff1","bold":true}},{"insert":" at the bottom right for help and support.\n\nLike AppFlowy? Follow us:"},{"insert":"\n","attributes":{"header":2}},{"insert":"GitHub: https://github.com/AppFlowy-IO/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Twitter: https://twitter.com/appflowy"},{"insert":"\n","attributes":{"blockquote":true}},{"insert":"Newsletter: https://www.appflowy.io/blog"},{"insert":"\n","attributes":{"blockquote":true}}] \ No newline at end of file diff --git a/shared-lib/flowy-sync/src/client_document/default/mod.rs b/shared-lib/flowy-sync/src/client_document/default/mod.rs deleted file mode 100644 index a6303081b8..0000000000 --- a/shared-lib/flowy-sync/src/client_document/default/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -use lib_ot::{core::OperationBuilder, text_delta::TextOperations}; - -#[inline] -pub fn initial_document_operations() -> TextOperations { - OperationBuilder::new().insert("\n").build() -} - -#[inline] -pub fn initial_document_str() -> String { - initial_document_operations().json_str() -} - -#[inline] -pub fn initial_read_me() -> TextOperations { - let json = include_str!("READ_ME.json"); - TextOperations::from_json(json).unwrap() -} - -#[cfg(test)] -mod tests { - use crate::client_document::default::initial_read_me; - - #[test] - fn load_read_me() { - println!("{}", initial_read_me().json_str()); - } -} diff --git a/shared-lib/flowy-sync/src/client_document/document_pad.rs b/shared-lib/flowy-sync/src/client_document/document_pad.rs index 176cf0d02a..7611c471e1 100644 --- a/shared-lib/flowy-sync/src/client_document/document_pad.rs +++ b/shared-lib/flowy-sync/src/client_document/document_pad.rs @@ -1,4 +1,3 @@ -use crate::client_document::default::initial_document_str; use crate::{ client_document::{ history::{History, UndoResult}, @@ -7,29 +6,35 @@ use crate::{ errors::CollaborateError, }; use bytes::Bytes; -use lib_ot::{core::*, text_delta::TextOperations}; +use flowy_http_model::util::md5; +use lib_ot::text_delta::DeltaTextOperationBuilder; +use lib_ot::{core::*, text_delta::DeltaTextOperations}; use tokio::sync::mpsc; pub trait InitialDocument { fn json_str() -> String; } -pub struct EmptyDoc(); -impl InitialDocument for EmptyDoc { +pub struct EmptyDocument(); +impl InitialDocument for EmptyDocument { fn json_str() -> String { - TextOperations::default().json_str() + DeltaTextOperations::default().json_str() } } -pub struct NewlineDoc(); -impl InitialDocument for NewlineDoc { +pub struct NewlineDocument(); +impl InitialDocument for NewlineDocument { fn json_str() -> String { - initial_document_str() + initial_delta_document_content() } } +pub fn initial_delta_document_content() -> String { + DeltaTextOperationBuilder::new().insert("\n").build().json_str() +} + pub struct ClientDocument { - operations: TextOperations, + operations: DeltaTextOperations, history: History, view: ViewExtensions, last_edit_time: usize, @@ -42,7 +47,7 @@ impl ClientDocument { Self::from_json(&content).unwrap() } - pub fn from_operations(operations: TextOperations) -> Self { + pub fn from_operations(operations: DeltaTextOperations) -> Self { ClientDocument { operations, history: History::new(), @@ -53,7 +58,7 @@ impl ClientDocument { } pub fn from_json(json: &str) -> Result { - let operations = TextOperations::from_json(json)?; + let operations = DeltaTextOperations::from_json(json)?; Ok(Self::from_operations(operations)) } @@ -69,20 +74,20 @@ impl ClientDocument { self.operations.content().unwrap() } - pub fn get_operations(&self) -> &TextOperations { + pub fn get_operations(&self) -> &DeltaTextOperations { &self.operations } - pub fn md5(&self) -> String { + pub fn document_md5(&self) -> String { let bytes = self.to_bytes(); - format!("{:x}", md5::compute(bytes)) + md5(&bytes) } pub fn set_notify(&mut self, notify: mpsc::UnboundedSender<()>) { self.notify = Some(notify); } - pub fn set_operations(&mut self, operations: TextOperations) { + pub fn set_operations(&mut self, operations: DeltaTextOperations) { tracing::trace!("document: {}", operations.json_str()); self.operations = operations; @@ -94,7 +99,7 @@ impl ClientDocument { } } - pub fn compose_operations(&mut self, operations: TextOperations) -> Result<(), CollaborateError> { + pub fn compose_operations(&mut self, operations: DeltaTextOperations) -> Result<(), CollaborateError> { tracing::trace!("{} compose {}", &self.operations.json_str(), operations.json_str()); let composed_operations = self.operations.compose(&operations)?; let mut undo_operations = operations.invert(&self.operations); @@ -120,7 +125,7 @@ impl ClientDocument { Ok(()) } - pub fn insert(&mut self, index: usize, data: T) -> Result { + pub fn insert(&mut self, index: usize, data: T) -> Result { let text = data.to_string(); let interval = Interval::new(index, index); let _ = validate_interval(&self.operations, &interval)?; @@ -129,7 +134,7 @@ impl ClientDocument { Ok(operations) } - pub fn delete(&mut self, interval: Interval) -> Result { + pub fn delete(&mut self, interval: Interval) -> Result { let _ = validate_interval(&self.operations, &interval)?; debug_assert!(!interval.is_empty()); let operations = self.view.delete(&self.operations, interval)?; @@ -143,7 +148,7 @@ impl ClientDocument { &mut self, interval: Interval, attribute: AttributeEntry, - ) -> Result { + ) -> Result { let _ = validate_interval(&self.operations, &interval)?; tracing::trace!("format {} with {:?}", interval, attribute); let operations = self.view.format(&self.operations, attribute, interval).unwrap(); @@ -151,9 +156,13 @@ impl ClientDocument { Ok(operations) } - pub fn replace(&mut self, interval: Interval, data: T) -> Result { + pub fn replace( + &mut self, + interval: Interval, + data: T, + ) -> Result { let _ = validate_interval(&self.operations, &interval)?; - let mut operations = TextOperations::default(); + let mut operations = DeltaTextOperations::default(); let text = data.to_string(); if !text.is_empty() { operations = self.view.insert(&self.operations, &text, interval)?; @@ -206,12 +215,15 @@ impl ClientDocument { pub fn is_empty(&self) -> bool { // The document is empty if its text is equal to the initial text. - self.operations.json_str() == NewlineDoc::json_str() + self.operations.json_str() == NewlineDocument::json_str() } } impl ClientDocument { - fn invert(&self, operations: &TextOperations) -> Result<(TextOperations, TextOperations), CollaborateError> { + fn invert( + &self, + operations: &DeltaTextOperations, + ) -> Result<(DeltaTextOperations, DeltaTextOperations), CollaborateError> { // c = a.compose(b) // d = b.invert(a) // a = c.compose(d) @@ -221,7 +233,7 @@ impl ClientDocument { } } -fn validate_interval(operations: &TextOperations, interval: &Interval) -> Result<(), CollaborateError> { +fn validate_interval(operations: &DeltaTextOperations, interval: &Interval) -> Result<(), CollaborateError> { if operations.utf16_target_len < interval.end { log::error!( "{:?} out of bounds. should 0..{}", diff --git a/shared-lib/flowy-sync/src/client_document/extensions/delete/default_delete.rs b/shared-lib/flowy-sync/src/client_document/extensions/delete/default_delete.rs index 5fcf100299..208786a61d 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/delete/default_delete.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/delete/default_delete.rs @@ -1,7 +1,7 @@ use crate::client_document::DeleteExt; use lib_ot::{ - core::{Interval, OperationBuilder}, - text_delta::TextOperations, + core::{DeltaOperationBuilder, Interval}, + text_delta::DeltaTextOperations, }; pub struct DefaultDelete {} @@ -10,9 +10,9 @@ impl DeleteExt for DefaultDelete { "DefaultDelete" } - fn apply(&self, _delta: &TextOperations, interval: Interval) -> Option { + fn apply(&self, _delta: &DeltaTextOperations, interval: Interval) -> Option { Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(interval.start) .delete(interval.size()) .build(), diff --git a/shared-lib/flowy-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs b/shared-lib/flowy-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs index 0f30102616..2cf75187c3 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/delete/preserve_line_format_merge.rs @@ -1,7 +1,7 @@ use crate::{client_document::DeleteExt, util::is_newline}; use lib_ot::{ - core::{Interval, OperationAttributes, OperationBuilder, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, - text_delta::{empty_attributes, TextOperations}, + core::{DeltaOperationBuilder, Interval, OperationAttributes, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, + text_delta::{empty_attributes, DeltaTextOperations}, }; pub struct PreserveLineFormatOnMerge {} @@ -10,7 +10,7 @@ impl DeleteExt for PreserveLineFormatOnMerge { "PreserveLineFormatOnMerge" } - fn apply(&self, delta: &TextOperations, interval: Interval) -> Option { + fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option { if interval.is_empty() { return None; } @@ -25,7 +25,7 @@ impl DeleteExt for PreserveLineFormatOnMerge { } iter.seek::(interval.size() - 1); - let mut new_delta = OperationBuilder::new() + let mut new_delta = DeltaOperationBuilder::new() .retain(interval.start) .delete(interval.size()) .build(); diff --git a/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_block_format.rs b/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_block_format.rs index c2c2006e3e..680db108f4 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_block_format.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_block_format.rs @@ -1,8 +1,8 @@ use lib_ot::core::AttributeEntry; use lib_ot::text_delta::is_block; use lib_ot::{ - core::{Interval, OperationBuilder, OperationIterator}, - text_delta::{empty_attributes, AttributeScope, TextOperations}, + core::{DeltaOperationBuilder, Interval, OperationIterator}, + text_delta::{empty_attributes, AttributeScope, DeltaTextOperations}, }; use crate::{ @@ -16,12 +16,17 @@ impl FormatExt for ResolveBlockFormat { "ResolveBlockFormat" } - fn apply(&self, delta: &TextOperations, interval: Interval, attribute: &AttributeEntry) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option { if !is_block(&attribute.key) { return None; } - let mut new_delta = OperationBuilder::new().retain(interval.start).build(); + let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); let mut iter = OperationIterator::from_offset(delta, interval.start); let mut start = 0; let end = interval.size(); diff --git a/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_inline_format.rs b/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_inline_format.rs index 7ee82dcadc..6a3c0d23fa 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_inline_format.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/format/resolve_inline_format.rs @@ -1,8 +1,8 @@ use lib_ot::core::AttributeEntry; use lib_ot::text_delta::is_inline; use lib_ot::{ - core::{Interval, OperationBuilder, OperationIterator}, - text_delta::{AttributeScope, TextOperations}, + core::{DeltaOperationBuilder, Interval, OperationIterator}, + text_delta::{AttributeScope, DeltaTextOperations}, }; use crate::{ @@ -16,11 +16,16 @@ impl FormatExt for ResolveInlineFormat { "ResolveInlineFormat" } - fn apply(&self, delta: &TextOperations, interval: Interval, attribute: &AttributeEntry) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option { if !is_inline(&attribute.key) { return None; } - let mut new_delta = OperationBuilder::new().retain(interval.start).build(); + let mut new_delta = DeltaOperationBuilder::new().retain(interval.start).build(); let mut iter = OperationIterator::from_offset(delta, interval.start); let mut start = 0; let end = interval.size(); diff --git a/shared-lib/flowy-sync/src/client_document/extensions/helper.rs b/shared-lib/flowy-sync/src/client_document/extensions/helper.rs index 6e267f0cd0..4ecb23dcf6 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/helper.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/helper.rs @@ -1,9 +1,13 @@ use crate::util::find_newline; use lib_ot::core::AttributeEntry; -use lib_ot::text_delta::{empty_attributes, AttributeScope, TextOperation, TextOperations}; +use lib_ot::text_delta::{empty_attributes, AttributeScope, DeltaTextOperation, DeltaTextOperations}; -pub(crate) fn line_break(op: &TextOperation, attribute: &AttributeEntry, scope: AttributeScope) -> TextOperations { - let mut new_delta = TextOperations::new(); +pub(crate) fn line_break( + op: &DeltaTextOperation, + attribute: &AttributeEntry, + scope: AttributeScope, +) -> DeltaTextOperations { + let mut new_delta = DeltaTextOperations::new(); let mut start = 0; let end = op.len(); let mut s = op.get_data(); diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_exit_block.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_exit_block.rs index 52ed6165cd..31f40545f9 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_exit_block.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_exit_block.rs @@ -1,6 +1,6 @@ use crate::{client_document::InsertExt, util::is_newline}; -use lib_ot::core::{is_empty_line_at_index, OperationBuilder, OperationIterator}; -use lib_ot::text_delta::{attributes_except_header, BuildInTextAttributeKey, TextOperations}; +use lib_ot::core::{is_empty_line_at_index, DeltaOperationBuilder, OperationIterator}; +use lib_ot::text_delta::{attributes_except_header, BuildInTextAttributeKey, DeltaTextOperations}; pub struct AutoExitBlock {} @@ -9,7 +9,13 @@ impl InsertExt for AutoExitBlock { "AutoExitBlock" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { // Auto exit block will be triggered by enter two new lines if !is_newline(text) { return None; @@ -45,7 +51,7 @@ impl InsertExt for AutoExitBlock { attributes.retain_values(&[BuildInTextAttributeKey::Header.as_ref()]); Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(index + replace_len) .retain_with_attributes(1, attributes) .build(), diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_format.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_format.rs index 2fe7b70b0e..e62fcff287 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_format.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/auto_format.rs @@ -1,8 +1,8 @@ use crate::{client_document::InsertExt, util::is_whitespace}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{count_utf16_code_units, OperationBuilder, OperationIterator}, - text_delta::{empty_attributes, BuildInTextAttribute, TextOperations}, + core::{count_utf16_code_units, DeltaOperationBuilder, OperationIterator}, + text_delta::{empty_attributes, BuildInTextAttribute, DeltaTextOperations}, }; use std::cmp::min; use url::Url; @@ -13,7 +13,13 @@ impl InsertExt for AutoFormatExt { "AutoFormatExt" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { // enter whitespace to trigger auto format if !is_whitespace(text) { return None; @@ -42,7 +48,7 @@ impl InsertExt for AutoFormatExt { }; return Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(index + replace_len - min(index, format_len)) .retain_with_attributes(format_len, format_attributes) .insert_with_attributes(text, next_attributes) diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/default_insert.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/default_insert.rs index 948cf7f994..78248b6877 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/default_insert.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/default_insert.rs @@ -1,8 +1,8 @@ use crate::client_document::InsertExt; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{OperationAttributes, OperationBuilder, OperationIterator, NEW_LINE}, - text_delta::{BuildInTextAttributeKey, TextOperations}, + core::{DeltaOperationBuilder, OperationAttributes, OperationIterator, NEW_LINE}, + text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct DefaultInsertAttribute {} @@ -11,7 +11,13 @@ impl InsertExt for DefaultInsertAttribute { "DefaultInsertAttribute" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { let iter = OperationIterator::new(delta); let mut attributes = AttributeHashMap::new(); @@ -35,7 +41,7 @@ impl InsertExt for DefaultInsertAttribute { } Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(index + replace_len) .insert_with_attributes(text, attributes) .build(), diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/mod.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/mod.rs index 19661006e6..3ec97f37d8 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/mod.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/mod.rs @@ -2,7 +2,7 @@ use crate::client_document::InsertExt; pub use auto_exit_block::*; pub use auto_format::*; pub use default_insert::*; -use lib_ot::text_delta::TextOperations; +use lib_ot::text_delta::DeltaTextOperations; pub use preserve_block_format::*; pub use preserve_inline_format::*; pub use reset_format_on_new_line::*; @@ -22,11 +22,11 @@ impl InsertExt for InsertEmbedsExt { fn apply( &self, - _delta: &TextOperations, + _delta: &DeltaTextOperations, _replace_len: usize, _text: &str, _index: usize, - ) -> Option { + ) -> Option { None } } @@ -39,11 +39,11 @@ impl InsertExt for ForceNewlineForInsertsAroundEmbedExt { fn apply( &self, - _delta: &TextOperations, + _delta: &DeltaTextOperations, _replace_len: usize, _text: &str, _index: usize, - ) -> Option { + ) -> Option { None } } diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_block_format.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_block_format.rs index 3ce1d1f9f0..46859cdb1f 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_block_format.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_block_format.rs @@ -1,8 +1,8 @@ use crate::{client_document::InsertExt, util::is_newline}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{OperationBuilder, OperationIterator, NEW_LINE}, - text_delta::{attributes_except_header, empty_attributes, BuildInTextAttributeKey, TextOperations}, + core::{DeltaOperationBuilder, OperationIterator, NEW_LINE}, + text_delta::{attributes_except_header, empty_attributes, BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct PreserveBlockFormatOnInsert {} @@ -11,7 +11,13 @@ impl InsertExt for PreserveBlockFormatOnInsert { "PreserveBlockFormatOnInsert" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { if !is_newline(text) { return None; } @@ -32,7 +38,7 @@ impl InsertExt for PreserveBlockFormatOnInsert { } let lines: Vec<_> = text.split(NEW_LINE).collect(); - let mut new_delta = OperationBuilder::new().retain(index + replace_len).build(); + let mut new_delta = DeltaOperationBuilder::new().retain(index + replace_len).build(); lines.iter().enumerate().for_each(|(i, line)| { if !line.is_empty() { new_delta.insert(line, empty_attributes()); diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_inline_format.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_inline_format.rs index f8bd30dc9b..d7f238a21e 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_inline_format.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/preserve_inline_format.rs @@ -3,8 +3,8 @@ use crate::{ util::{contain_newline, is_newline}, }; use lib_ot::{ - core::{OpNewline, OperationBuilder, OperationIterator, NEW_LINE}, - text_delta::{empty_attributes, BuildInTextAttributeKey, TextOperations}, + core::{DeltaOperationBuilder, OpNewline, OperationIterator, NEW_LINE}, + text_delta::{empty_attributes, BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct PreserveInlineFormat {} @@ -13,7 +13,13 @@ impl InsertExt for PreserveInlineFormat { "PreserveInlineFormat" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { if contain_newline(text) { return None; } @@ -27,7 +33,7 @@ impl InsertExt for PreserveInlineFormat { let mut attributes = prev.get_attributes(); if attributes.is_empty() || !attributes.contains_key(BuildInTextAttributeKey::Link.as_ref()) { return Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(index + replace_len) .insert_with_attributes(text, attributes) .build(), @@ -44,7 +50,7 @@ impl InsertExt for PreserveInlineFormat { } } - let new_delta = OperationBuilder::new() + let new_delta = DeltaOperationBuilder::new() .retain(index + replace_len) .insert_with_attributes(text, attributes) .build(); @@ -59,7 +65,13 @@ impl InsertExt for PreserveLineFormatOnSplit { "PreserveLineFormatOnSplit" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { if !is_newline(text) { return None; } @@ -76,7 +88,7 @@ impl InsertExt for PreserveLineFormatOnSplit { return None; } - let mut new_delta = TextOperations::new(); + let mut new_delta = DeltaTextOperations::new(); new_delta.retain(index + replace_len, empty_attributes()); if newline_status.is_contain() { diff --git a/shared-lib/flowy-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs b/shared-lib/flowy-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs index 3a15e9dba0..067e373212 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/insert/reset_format_on_new_line.rs @@ -1,8 +1,8 @@ use crate::{client_document::InsertExt, util::is_newline}; use lib_ot::core::AttributeHashMap; use lib_ot::{ - core::{OperationBuilder, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, - text_delta::{BuildInTextAttributeKey, TextOperations}, + core::{DeltaOperationBuilder, OperationIterator, Utf16CodeUnitMetric, NEW_LINE}, + text_delta::{BuildInTextAttributeKey, DeltaTextOperations}, }; pub struct ResetLineFormatOnNewLine {} @@ -11,7 +11,13 @@ impl InsertExt for ResetLineFormatOnNewLine { "ResetLineFormatOnNewLine" } - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option { + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option { if !is_newline(text) { return None; } @@ -33,7 +39,7 @@ impl InsertExt for ResetLineFormatOnNewLine { let len = index + replace_len; Some( - OperationBuilder::new() + DeltaOperationBuilder::new() .retain(len) .insert_with_attributes(NEW_LINE, next_op.get_attributes()) .retain_with_attributes(1, reset_attribute) diff --git a/shared-lib/flowy-sync/src/client_document/extensions/mod.rs b/shared-lib/flowy-sync/src/client_document/extensions/mod.rs index a2cd2dc9d7..6cfc1f48ac 100644 --- a/shared-lib/flowy-sync/src/client_document/extensions/mod.rs +++ b/shared-lib/flowy-sync/src/client_document/extensions/mod.rs @@ -2,7 +2,7 @@ pub use delete::*; pub use format::*; pub use insert::*; use lib_ot::core::AttributeEntry; -use lib_ot::{core::Interval, text_delta::TextOperations}; +use lib_ot::{core::Interval, text_delta::DeltaTextOperations}; mod delete; mod format; @@ -15,15 +15,26 @@ pub type DeleteExtension = Box; pub trait InsertExt { fn ext_name(&self) -> &str; - fn apply(&self, delta: &TextOperations, replace_len: usize, text: &str, index: usize) -> Option; + fn apply( + &self, + delta: &DeltaTextOperations, + replace_len: usize, + text: &str, + index: usize, + ) -> Option; } pub trait FormatExt { fn ext_name(&self) -> &str; - fn apply(&self, delta: &TextOperations, interval: Interval, attribute: &AttributeEntry) -> Option; + fn apply( + &self, + delta: &DeltaTextOperations, + interval: Interval, + attribute: &AttributeEntry, + ) -> Option; } pub trait DeleteExt { fn ext_name(&self) -> &str; - fn apply(&self, delta: &TextOperations, interval: Interval) -> Option; + fn apply(&self, delta: &DeltaTextOperations, interval: Interval) -> Option; } diff --git a/shared-lib/flowy-sync/src/client_document/history.rs b/shared-lib/flowy-sync/src/client_document/history.rs index 4d08a8238d..07930f3973 100644 --- a/shared-lib/flowy-sync/src/client_document/history.rs +++ b/shared-lib/flowy-sync/src/client_document/history.rs @@ -1,18 +1,18 @@ -use lib_ot::text_delta::TextOperations; +use lib_ot::text_delta::DeltaTextOperations; const MAX_UNDOES: usize = 20; #[derive(Debug, Clone)] pub struct UndoResult { - pub operations: TextOperations, + pub operations: DeltaTextOperations, } #[derive(Debug, Clone)] pub struct History { #[allow(dead_code)] cur_undo: usize, - undoes: Vec, - redoes: Vec, + undoes: Vec, + redoes: Vec, capacity: usize, } @@ -40,15 +40,15 @@ impl History { !self.redoes.is_empty() } - pub fn add_undo(&mut self, delta: TextOperations) { + pub fn add_undo(&mut self, delta: DeltaTextOperations) { self.undoes.push(delta); } - pub fn add_redo(&mut self, delta: TextOperations) { + pub fn add_redo(&mut self, delta: DeltaTextOperations) { self.redoes.push(delta); } - pub fn record(&mut self, delta: TextOperations) { + pub fn record(&mut self, delta: DeltaTextOperations) { if delta.ops.is_empty() { return; } @@ -61,7 +61,7 @@ impl History { } } - pub fn undo(&mut self) -> Option { + pub fn undo(&mut self) -> Option { if !self.can_undo() { return None; } @@ -69,7 +69,7 @@ impl History { Some(delta) } - pub fn redo(&mut self) -> Option { + pub fn redo(&mut self) -> Option { if !self.can_redo() { return None; } diff --git a/shared-lib/flowy-sync/src/client_document/mod.rs b/shared-lib/flowy-sync/src/client_document/mod.rs index 7e52b8f9e8..d571d5447b 100644 --- a/shared-lib/flowy-sync/src/client_document/mod.rs +++ b/shared-lib/flowy-sync/src/client_document/mod.rs @@ -4,7 +4,6 @@ pub use document_pad::*; pub(crate) use extensions::*; pub use view::*; -pub mod default; mod document_pad; mod extensions; pub mod history; diff --git a/shared-lib/flowy-sync/src/client_document/view.rs b/shared-lib/flowy-sync/src/client_document/view.rs index 7c06e36bb8..e009f1ae45 100644 --- a/shared-lib/flowy-sync/src/client_document/view.rs +++ b/shared-lib/flowy-sync/src/client_document/view.rs @@ -3,7 +3,7 @@ use lib_ot::core::AttributeEntry; use lib_ot::{ core::{trim, Interval}, errors::{ErrorBuilder, OTError, OTErrorCode}, - text_delta::TextOperations, + text_delta::DeltaTextOperations, }; pub const RECORD_THRESHOLD: usize = 400; // in milliseconds @@ -25,10 +25,10 @@ impl ViewExtensions { pub(crate) fn insert( &self, - operations: &TextOperations, + operations: &DeltaTextOperations, text: &str, interval: Interval, - ) -> Result { + ) -> Result { let mut new_operations = None; for ext in &self.insert_exts { if let Some(mut operations) = ext.apply(operations, interval.size(), text, interval.start) { @@ -45,7 +45,11 @@ impl ViewExtensions { } } - pub(crate) fn delete(&self, delta: &TextOperations, interval: Interval) -> Result { + pub(crate) fn delete( + &self, + delta: &DeltaTextOperations, + interval: Interval, + ) -> Result { let mut new_delta = None; for ext in &self.delete_exts { if let Some(mut delta) = ext.apply(delta, interval) { @@ -64,10 +68,10 @@ impl ViewExtensions { pub(crate) fn format( &self, - operations: &TextOperations, + operations: &DeltaTextOperations, attribute: AttributeEntry, interval: Interval, - ) -> Result { + ) -> Result { let mut new_operations = None; for ext in &self.format_exts { if let Some(mut operations) = ext.apply(operations, interval, &attribute) { diff --git a/shared-lib/flowy-sync/src/client_folder/builder.rs b/shared-lib/flowy-sync/src/client_folder/builder.rs index 972051a462..5098960d64 100644 --- a/shared-lib/flowy-sync/src/client_folder/builder.rs +++ b/shared-lib/flowy-sync/src/client_folder/builder.rs @@ -1,12 +1,12 @@ use crate::util::make_operations_from_revisions; use crate::{ client_folder::{default_folder_operations, FolderPad}, - entities::revision::Revision, errors::CollaborateResult, }; use crate::server_folder::FolderOperations; -use flowy_folder_data_model::revision::{TrashRevision, WorkspaceRevision}; +use flowy_http_model::revision::Revision; +use folder_rev_model::{TrashRevision, WorkspaceRevision}; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize)] diff --git a/shared-lib/flowy-sync/src/client_folder/folder_pad.rs b/shared-lib/flowy-sync/src/client_folder/folder_pad.rs index 6b3e27a9b2..704f88c2be 100644 --- a/shared-lib/flowy-sync/src/client_folder/folder_pad.rs +++ b/shared-lib/flowy-sync/src/client_folder/folder_pad.rs @@ -1,19 +1,18 @@ use crate::errors::internal_error; -use crate::server_folder::FolderOperations; +use crate::server_folder::{FolderOperations, FolderOperationsBuilder}; use crate::util::cal_diff; use crate::{ client_folder::builder::FolderPadBuilder, - entities::revision::{md5, Revision}, errors::{CollaborateError, CollaborateResult}, }; -use flowy_folder_data_model::revision::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; +use flowy_http_model::revision::Revision; +use flowy_http_model::util::md5; +use folder_rev_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use lib_infra::util::move_vec_element; use lib_ot::core::*; use serde::Deserialize; use std::sync::Arc; -pub type FolderOperationsBuilder = DeltaBuilder; - #[derive(Debug, Clone, Eq, PartialEq)] pub struct FolderPad { folder_rev: FolderRevision, @@ -63,7 +62,7 @@ impl FolderPad { self.folder_rev = folder.folder_rev; self.operations = folder.operations; - Ok(self.md5()) + Ok(self.folder_md5()) } pub fn compose_remote_operations(&mut self, operations: FolderOperations) -> CollaborateResult { @@ -260,9 +259,8 @@ impl FolderPad { } #[tracing::instrument(level = "trace", skip(self), err)] - pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult> { - let view = self.read_view(view_id)?; - self.with_app(&view.app_id, |app| { + pub fn delete_view(&mut self, app_id: &str, view_id: &str) -> CollaborateResult> { + self.with_app(app_id, |app| { app.belongings.retain(|view| view.id != view_id); Ok(Some(())) }) @@ -316,7 +314,7 @@ impl FolderPad { } } - pub fn md5(&self) -> String { + pub fn folder_md5(&self) -> String { md5(&self.operations.json_bytes()) } @@ -348,7 +346,7 @@ impl FolderPad { self.operations = self.operations.compose(&operations)?; Ok(Some(FolderChangeset { operations, - md5: self.md5(), + md5: self.folder_md5(), })) } } @@ -386,7 +384,7 @@ impl FolderPad { self.operations = self.operations.compose(&operations)?; Ok(Some(FolderChangeset { operations, - md5: self.md5(), + md5: self.folder_md5(), })) } } @@ -464,15 +462,11 @@ pub struct FolderChangeset { mod tests { #![allow(clippy::all)] use crate::client_folder::folder_pad::FolderPad; + use crate::server_folder::{FolderOperations, FolderOperationsBuilder}; use chrono::Utc; - use serde::Deserialize; - - use crate::client_folder::FolderOperationsBuilder; - use crate::server_folder::FolderOperations; - use flowy_folder_data_model::revision::{ - AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision, - }; + use folder_rev_model::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision}; use lib_ot::core::OperationTransform; + use serde::Deserialize; #[test] fn folder_add_workspace() { @@ -728,7 +722,7 @@ mod tests { #[test] fn folder_delete_view() { let (mut folder, initial_operations, view) = test_view_folder(); - let operations = folder.delete_view(&view.id).unwrap().unwrap().operations; + let operations = folder.delete_view(&view.app_id, &view.id).unwrap().unwrap().operations; let new_folder = make_folder_from_operations(initial_operations, vec![operations]); assert_folder_equal( diff --git a/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs index 36f65837c0..85133c034a 100644 --- a/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/block_revision_pad.rs @@ -1,9 +1,8 @@ -use crate::entities::revision::{md5, RepeatedRevision, Revision}; use crate::errors::{CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_operations_from_revisions}; -use flowy_grid_data_model::revision::{ - gen_block_id, gen_row_id, CellRevision, GridBlockRevision, RowChangeset, RowRevision, -}; +use flowy_http_model::revision::{RepeatedRevision, Revision}; +use flowy_http_model::util::md5; +use grid_rev_model::{gen_block_id, gen_row_id, CellRevision, GridBlockRevision, RowChangeset, RowRevision}; use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; use std::borrow::Cow; use std::collections::HashMap; @@ -256,10 +255,10 @@ pub fn make_grid_block_operations(block_rev: &GridBlockRevision) -> GridBlockOpe GridBlockOperationsBuilder::new().insert(&json).build() } -pub fn make_grid_block_revisions(user_id: &str, grid_block_meta_data: &GridBlockRevision) -> RepeatedRevision { +pub fn make_grid_block_revisions(_user_id: &str, grid_block_meta_data: &GridBlockRevision) -> RepeatedRevision { let operations = make_grid_block_operations(grid_block_meta_data); let bytes = operations.json_bytes(); - let revision = Revision::initial_revision(user_id, &grid_block_meta_data.block_id, bytes); + let revision = Revision::initial_revision(&grid_block_meta_data.block_id, bytes); revision.into() } @@ -281,7 +280,7 @@ impl std::default::Default for GridBlockRevisionPad { #[cfg(test)] mod tests { use crate::client_grid::{GridBlockOperations, GridBlockRevisionPad}; - use flowy_grid_data_model::revision::{RowChangeset, RowRevision}; + use grid_rev_model::{RowChangeset, RowRevision}; use std::borrow::Cow; diff --git a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs index ad3f443505..31ac7dfa50 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs @@ -1,7 +1,5 @@ use crate::errors::{CollaborateError, CollaborateResult}; -use flowy_grid_data_model::revision::{ - BuildGridContext, FieldRevision, GridBlockMetaRevision, GridBlockRevision, RowRevision, -}; +use grid_rev_model::{BuildGridContext, FieldRevision, GridBlockMetaRevision, GridBlockRevision, RowRevision}; use std::sync::Arc; pub struct GridBuilder { diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs index fa9ffd38ff..7e14a73eec 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs @@ -1,17 +1,18 @@ -use crate::entities::revision::{md5, RepeatedRevision, Revision}; use crate::errors::{internal_error, CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_operations_from_revisions}; - -use flowy_grid_data_model::revision::{ +use flowy_http_model::revision::{RepeatedRevision, Revision}; +use flowy_http_model::util::md5; +use grid_rev_model::{ gen_block_id, gen_grid_id, FieldRevision, FieldTypeRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, GridRevision, }; use lib_infra::util::move_vec_element; -use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; +use lib_ot::core::{DeltaOperationBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; use std::collections::HashMap; use std::sync::Arc; + pub type GridOperations = DeltaOperations; -pub type GridOperationsBuilder = DeltaBuilder; +pub type GridOperationsBuilder = DeltaOperationBuilder; pub struct GridRevisionPad { grid_rev: Arc, @@ -163,7 +164,7 @@ impl GridRevisionPad { } Some(field_rev) => { let mut_field_rev = Arc::make_mut(field_rev); - let old_field_type_rev = mut_field_rev.ty.clone(); + let old_field_type_rev = mut_field_rev.ty; let old_field_type_option = mut_field_rev.get_type_option_str(mut_field_rev.ty); match mut_field_rev.get_type_option_str(new_field_type) { Some(new_field_type_option) => { @@ -188,14 +189,6 @@ impl GridRevisionPad { }) } - pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { - self.grid_rev - .fields - .iter() - .enumerate() - .find(|(_, field)| field.id == field_id) - } - pub fn replace_field_rev( &mut self, field_rev: Arc, @@ -237,6 +230,14 @@ impl GridRevisionPad { self.grid_rev.fields.iter().any(|field| field.id == field_id) } + pub fn get_field_rev(&self, field_id: &str) -> Option<(usize, &Arc)> { + self.grid_rev + .fields + .iter() + .enumerate() + .find(|(_, field)| field.id == field_id) + } + pub fn get_field_revs(&self, field_ids: Option>) -> CollaborateResult>> { match field_ids { None => Ok(self.grid_rev.fields.clone()), @@ -315,7 +316,7 @@ impl GridRevisionPad { }) } - pub fn md5(&self) -> String { + pub fn grid_md5(&self) -> String { md5(&self.operations.json_bytes()) } @@ -343,7 +344,7 @@ impl GridRevisionPad { self.operations = self.operations.compose(&operations)?; Ok(Some(GridRevisionChangeset { operations, - md5: self.md5(), + md5: self.grid_md5(), })) } } @@ -409,10 +410,10 @@ pub fn make_grid_operations(grid_rev: &GridRevision) -> GridOperations { GridOperationsBuilder::new().insert(&json).build() } -pub fn make_grid_revisions(user_id: &str, grid_rev: &GridRevision) -> RepeatedRevision { +pub fn make_grid_revisions(_user_id: &str, grid_rev: &GridRevision) -> RepeatedRevision { let operations = make_grid_operations(grid_rev); let bytes = operations.json_bytes(); - let revision = Revision::initial_revision(user_id, &grid_rev.grid_id, bytes); + let revision = Revision::initial_revision(&grid_rev.grid_id, bytes); revision.into() } diff --git a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs index 46e91734b4..43aa801d1f 100644 --- a/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs +++ b/shared-lib/flowy-sync/src/client_grid/view_revision_pad.rs @@ -1,9 +1,9 @@ -use crate::entities::revision::{md5, Revision}; use crate::errors::{internal_error, CollaborateError, CollaborateResult}; use crate::util::{cal_diff, make_operations_from_revisions}; -use flowy_grid_data_model::revision::{ - FieldRevision, FieldTypeRevision, FilterConfigurationRevision, FilterConfigurationsByFieldId, GridViewRevision, - GroupConfigurationRevision, GroupConfigurationsByFieldId, LayoutRevision, +use flowy_http_model::revision::Revision; +use flowy_http_model::util::md5; +use grid_rev_model::{ + FieldRevision, FieldTypeRevision, FilterRevision, GridViewRevision, GroupConfigurationRevision, LayoutRevision, }; use lib_ot::core::{DeltaBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; use std::sync::Arc; @@ -60,8 +60,12 @@ impl GridViewRevisionPad { Self::from_operations(view_id, operations) } - pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Option { - self.groups.get_objects_by_field_revs(field_revs) + pub fn get_groups_by_field_revs(&self, field_revs: &[Arc]) -> Vec> { + self.groups + .get_objects_by_field_revs(field_revs) + .into_values() + .flatten() + .collect() } pub fn get_all_groups(&self) -> Vec> { @@ -112,9 +116,9 @@ impl GridViewRevisionPad { pub fn delete_group( &mut self, + group_id: &str, field_id: &str, field_type: &FieldTypeRevision, - group_id: &str, ) -> CollaborateResult> { self.modify(|view| { if let Some(groups) = view.groups.get_mut_objects(field_id, field_type) { @@ -126,23 +130,23 @@ impl GridViewRevisionPad { }) } - pub fn get_all_filters(&self, field_revs: &[Arc]) -> Option { - self.filters.get_objects_by_field_revs(field_revs) + pub fn get_all_filters(&self, field_revs: &[Arc]) -> Vec> { + self.filters + .get_objects_by_field_revs(field_revs) + .into_values() + .flatten() + .collect() } - pub fn get_filters( - &self, - field_id: &str, - field_type_rev: &FieldTypeRevision, - ) -> Option>> { - self.filters.get_objects(field_id, field_type_rev) + pub fn get_filters(&self, field_id: &str, field_type_rev: &FieldTypeRevision) -> Vec> { + self.filters.get_objects(field_id, field_type_rev).unwrap_or_default() } pub fn insert_filter( &mut self, field_id: &str, field_type: &FieldTypeRevision, - filter_rev: FilterConfigurationRevision, + filter_rev: FilterRevision, ) -> CollaborateResult> { self.modify(|view| { view.filters.add_object(field_id, field_type, filter_rev); @@ -152,9 +156,9 @@ impl GridViewRevisionPad { pub fn delete_filter( &mut self, + filter_id: &str, field_id: &str, field_type: &FieldTypeRevision, - filter_id: &str, ) -> CollaborateResult> { self.modify(|view| { if let Some(filters) = view.filters.get_mut_objects(field_id, field_type) { diff --git a/shared-lib/flowy-sync/src/entities/parser/doc_id.rs b/shared-lib/flowy-sync/src/entities/parser/doc_id.rs deleted file mode 100644 index f11d1bba44..0000000000 --- a/shared-lib/flowy-sync/src/entities/parser/doc_id.rs +++ /dev/null @@ -1,18 +0,0 @@ -#[derive(Debug)] -pub struct DocumentIdentify(pub String); - -impl DocumentIdentify { - pub fn parse(s: String) -> Result { - if s.trim().is_empty() { - return Err("Doc id can not be empty or whitespace".to_string()); - } - - Ok(Self(s)) - } -} - -impl AsRef for DocumentIdentify { - fn as_ref(&self) -> &str { - &self.0 - } -} diff --git a/shared-lib/flowy-sync/src/entities/parser/mod.rs b/shared-lib/flowy-sync/src/entities/parser/mod.rs deleted file mode 100644 index 3ffedff877..0000000000 --- a/shared-lib/flowy-sync/src/entities/parser/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod doc_id; - -pub use doc_id::*; diff --git a/shared-lib/flowy-sync/src/lib.rs b/shared-lib/flowy-sync/src/lib.rs index b21023c903..a6235d0080 100644 --- a/shared-lib/flowy-sync/src/lib.rs +++ b/shared-lib/flowy-sync/src/lib.rs @@ -1,12 +1,10 @@ pub mod client_document; pub mod client_folder; pub mod client_grid; -pub mod entities; pub mod errors; -pub mod protobuf; pub mod server_document; pub mod server_folder; pub mod synchronizer; pub mod util; -pub use lib_ot::text_delta::TextOperations; +pub use lib_ot::text_delta::DeltaTextOperations; diff --git a/shared-lib/flowy-sync/src/server_document/document_manager.rs b/shared-lib/flowy-sync/src/server_document/document_manager.rs index 82dd4cb2e7..755929bf6f 100644 --- a/shared-lib/flowy-sync/src/server_document/document_manager.rs +++ b/shared-lib/flowy-sync/src/server_document/document_manager.rs @@ -1,18 +1,19 @@ -use crate::entities::revision::{RepeatedRevision, Revision}; use crate::{ - entities::{document::DocumentPayloadPB, ws_data::ServerRevisionWSDataBuilder}, errors::{internal_error, CollaborateError, CollaborateResult}, - protobuf::ClientRevisionWSData, server_document::document_pad::ServerDocument, synchronizer::{RevisionSyncPersistence, RevisionSyncResponse, RevisionSynchronizer, RevisionUser}, util::rev_id_from_str, }; use async_stream::stream; use dashmap::DashMap; +use flowy_http_model::document::DocumentPayloadPB; +use flowy_http_model::protobuf::ClientRevisionWSData; +use flowy_http_model::revision::{RepeatedRevision, Revision}; +use flowy_http_model::ws_data::ServerRevisionWSDataBuilder; use futures::stream::StreamExt; use lib_infra::future::BoxResultFuture; use lib_ot::core::AttributeHashMap; -use lib_ot::text_delta::TextOperations; +use lib_ot::text_delta::DeltaTextOperations; use std::{collections::HashMap, fmt::Debug, sync::Arc}; use tokio::{ sync::{mpsc, oneshot, RwLock}, @@ -216,7 +217,7 @@ impl OpenDocumentHandler { let (sender, receiver) = mpsc::channel(1000); let users = DashMap::new(); - let operations = TextOperations::from_bytes(&doc.content)?; + let operations = DeltaTextOperations::from_bytes(&doc.data)?; let sync_object = ServerDocument::from_operations(&doc_id, operations); let synchronizer = Arc::new(DocumentRevisionSynchronizer::new(doc.rev_id, sync_object, persistence)); @@ -270,7 +271,7 @@ impl OpenDocumentHandler { .send(msg) .await .map_err(|e| CollaborateError::internal().context(format!("Send document command failed: {}", e)))?; - Ok(rx.await.map_err(internal_error)?) + rx.await.map_err(internal_error) } } diff --git a/shared-lib/flowy-sync/src/server_document/document_pad.rs b/shared-lib/flowy-sync/src/server_document/document_pad.rs index ff2b567955..46e82e4fa7 100644 --- a/shared-lib/flowy-sync/src/server_document/document_pad.rs +++ b/shared-lib/flowy-sync/src/server_document/document_pad.rs @@ -1,20 +1,20 @@ use crate::synchronizer::RevisionOperations; use crate::{client_document::InitialDocument, errors::CollaborateError, synchronizer::RevisionSyncObject}; -use lib_ot::{core::*, text_delta::TextOperations}; +use lib_ot::{core::*, text_delta::DeltaTextOperations}; pub struct ServerDocument { document_id: String, - operations: TextOperations, + operations: DeltaTextOperations, } impl ServerDocument { #[allow(dead_code)] pub fn new(doc_id: &str) -> Self { - let operations = TextOperations::from_json(&C::json_str()).unwrap(); + let operations = DeltaTextOperations::from_json(&C::json_str()).unwrap(); Self::from_operations(doc_id, operations) } - pub fn from_operations(document_id: &str, operations: TextOperations) -> Self { + pub fn from_operations(document_id: &str, operations: DeltaTextOperations) -> Self { let document_id = document_id.to_owned(); ServerDocument { document_id, @@ -32,13 +32,16 @@ impl RevisionSyncObject for ServerDocument { self.operations.json_str() } - fn compose(&mut self, other: &TextOperations) -> Result<(), CollaborateError> { + fn compose(&mut self, other: &DeltaTextOperations) -> Result<(), CollaborateError> { let operations = self.operations.compose(other)?; self.operations = operations; Ok(()) } - fn transform(&self, other: &TextOperations) -> Result<(TextOperations, TextOperations), CollaborateError> { + fn transform( + &self, + other: &DeltaTextOperations, + ) -> Result<(DeltaTextOperations, DeltaTextOperations), CollaborateError> { let value = self.operations.transform(other)?; Ok(value) } diff --git a/shared-lib/flowy-sync/src/server_folder/folder_manager.rs b/shared-lib/flowy-sync/src/server_folder/folder_manager.rs index 43d624028c..15f62681c0 100644 --- a/shared-lib/flowy-sync/src/server_folder/folder_manager.rs +++ b/shared-lib/flowy-sync/src/server_folder/folder_manager.rs @@ -1,14 +1,15 @@ -use crate::entities::revision::{RepeatedRevision, Revision}; use crate::server_folder::folder_pad::{FolderOperations, FolderRevisionSynchronizer}; use crate::{ - entities::{folder::FolderInfo, ws_data::ServerRevisionWSDataBuilder}, errors::{internal_error, CollaborateError, CollaborateResult}, - protobuf::ClientRevisionWSData, server_folder::folder_pad::ServerFolder, synchronizer::{RevisionSyncPersistence, RevisionSyncResponse, RevisionUser}, util::rev_id_from_str, }; use async_stream::stream; +use flowy_http_model::folder::FolderInfo; +use flowy_http_model::protobuf::ClientRevisionWSData; +use flowy_http_model::revision::{RepeatedRevision, Revision}; +use flowy_http_model::ws_data::ServerRevisionWSDataBuilder; use futures::stream::StreamExt; use lib_infra::future::BoxResultFuture; use std::{collections::HashMap, fmt::Debug, sync::Arc}; @@ -241,7 +242,7 @@ impl OpenFolderHandler { .send(msg) .await .map_err(|e| CollaborateError::internal().context(format!("Send folder command failed: {}", e)))?; - Ok(rx.await.map_err(internal_error)?) + rx.await.map_err(internal_error) } } diff --git a/shared-lib/flowy-sync/src/server_folder/folder_pad.rs b/shared-lib/flowy-sync/src/server_folder/folder_pad.rs index f192cc7f3d..b979b95f87 100644 --- a/shared-lib/flowy-sync/src/server_folder/folder_pad.rs +++ b/shared-lib/flowy-sync/src/server_folder/folder_pad.rs @@ -1,9 +1,10 @@ use crate::synchronizer::{RevisionOperations, RevisionSynchronizer}; use crate::{errors::CollaborateError, synchronizer::RevisionSyncObject}; -use lib_ot::core::{DeltaOperations, EmptyAttributes, OperationTransform}; +use lib_ot::core::{DeltaOperationBuilder, DeltaOperations, EmptyAttributes, OperationTransform}; pub type FolderRevisionSynchronizer = RevisionSynchronizer; pub type FolderOperations = DeltaOperations; +pub type FolderOperationsBuilder = DeltaOperationBuilder; pub struct ServerFolder { folder_id: String, diff --git a/shared-lib/flowy-sync/src/synchronizer.rs b/shared-lib/flowy-sync/src/synchronizer.rs index 49bfaf5a77..6660721877 100644 --- a/shared-lib/flowy-sync/src/synchronizer.rs +++ b/shared-lib/flowy-sync/src/synchronizer.rs @@ -1,13 +1,6 @@ -use crate::entities::revision::{RepeatedRevision, Revision}; -use crate::{ - entities::{ - revision::RevisionRange, - ws_data::{ServerRevisionWSData, ServerRevisionWSDataBuilder}, - }, - errors::CollaborateError, - protobuf::Revision as RevisionPB, - util::*, -}; +use crate::{errors::CollaborateError, util::*}; +use flowy_http_model::revision::{RepeatedRevision, Revision, RevisionRange}; +use flowy_http_model::ws_data::{ServerRevisionWSData, ServerRevisionWSDataBuilder}; use lib_infra::future::BoxResultFuture; use lib_ot::core::{DeltaOperations, OperationAttributes}; use parking_lot::RwLock; @@ -207,7 +200,7 @@ where #[tracing::instrument(level = "debug", skip(self, revision))] fn transform_revision( &self, - revision: &RevisionPB, + revision: &flowy_http_model::protobuf::Revision, ) -> Result<(RevisionOperations, RevisionOperations), CollaborateError> { let client_operations = RevisionOperations::::from_bytes(&revision.bytes)?; let result = self.object.read().transform(&client_operations)?; diff --git a/shared-lib/flowy-sync/src/util.rs b/shared-lib/flowy-sync/src/util.rs index 225598c752..9a5d9ad5f4 100644 --- a/shared-lib/flowy-sync/src/util.rs +++ b/shared-lib/flowy-sync/src/util.rs @@ -1,20 +1,16 @@ +use crate::errors::{CollaborateError, CollaborateResult}; use crate::server_folder::FolderOperations; -use crate::{ - entities::{ - document::DocumentPayloadPB, - folder::FolderInfo, - revision::{RepeatedRevision, Revision}, - }, - errors::{CollaborateError, CollaborateResult}, -}; use dissimilar::Chunk; -use lib_ot::core::{OTString, OperationAttributes, OperationBuilder}; +use flowy_http_model::document::DocumentPayloadPB; +use flowy_http_model::folder::FolderInfo; +use flowy_http_model::revision::RepeatedRevision; +use flowy_http_model::revision::Revision; +use lib_ot::core::{DeltaOperationBuilder, OTString, OperationAttributes}; use lib_ot::{ core::{DeltaOperations, OperationTransform, NEW_LINE, WHITESPACE}, - text_delta::TextOperations, + text_delta::DeltaTextOperations, }; use serde::de::DeserializeOwned; -use std::sync::atomic::{AtomicI64, Ordering::SeqCst}; #[inline] pub fn find_newline(s: &str) -> Option { @@ -36,32 +32,6 @@ pub fn contain_newline(s: &str) -> bool { s.contains(NEW_LINE) } -#[inline] -pub fn md5>(data: T) -> String { - let md5 = format!("{:x}", md5::compute(data)); - md5 -} - -#[derive(Debug)] -pub struct RevIdCounter(pub AtomicI64); - -impl RevIdCounter { - pub fn new(n: i64) -> Self { - Self(AtomicI64::new(n)) - } - pub fn next(&self) -> i64 { - let _ = self.0.fetch_add(1, SeqCst); - self.value() - } - pub fn value(&self) -> i64 { - self.0.load(SeqCst) - } - - pub fn set(&self, n: i64) { - let _ = self.0.fetch_update(SeqCst, SeqCst, |_| Some(n)); - } -} - #[tracing::instrument(level = "trace", skip(revisions), err)] pub fn make_operations_from_revisions(revisions: Vec) -> CollaborateResult> where @@ -98,21 +68,6 @@ pub fn pair_rev_id_from_revision_pbs(revisions: &[Revision]) -> (i64, i64) { } } -pub fn pair_rev_id_from_revisions(revisions: &[Revision]) -> (i64, i64) { - let mut rev_id = 0; - revisions.iter().for_each(|revision| { - if rev_id < revision.rev_id { - rev_id = revision.rev_id; - } - }); - - if rev_id > 0 { - (rev_id - 1, rev_id) - } else { - (0, rev_id) - } -} - #[inline] pub fn make_folder_from_revisions_pb( folder_id: &str, @@ -155,7 +110,7 @@ pub fn make_document_from_revision_pbs( return Ok(None); } - let mut delta = TextOperations::new(); + let mut delta = DeltaTextOperations::new(); let mut base_rev_id = 0; let mut rev_id = 0; for revision in revisions { @@ -166,15 +121,13 @@ pub fn make_document_from_revision_pbs( tracing::warn!("revision delta_data is empty"); } - let new_delta = TextOperations::from_bytes(revision.bytes)?; + let new_delta = DeltaTextOperations::from_bytes(revision.bytes)?; delta = delta.compose(&new_delta)?; } - let text = delta.json_str(); - Ok(Some(DocumentPayloadPB { doc_id: doc_id.to_owned(), - content: text, + data: delta.json_bytes().to_vec(), rev_id, base_rev_id, })) @@ -191,7 +144,7 @@ pub fn rev_id_from_str(s: &str) -> Result { pub fn cal_diff(old: String, new: String) -> Option> { let chunks = dissimilar::diff(&old, &new); - let mut delta_builder = OperationBuilder::::new(); + let mut delta_builder = DeltaOperationBuilder::::new(); for chunk in &chunks { match chunk { Chunk::Equal(s) => { diff --git a/shared-lib/folder-rev-model/Cargo.toml b/shared-lib/folder-rev-model/Cargo.toml new file mode 100644 index 0000000000..90976a13df --- /dev/null +++ b/shared-lib/folder-rev-model/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "folder-rev-model" +version = "0.1.0" +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bytes = "1.0" +strum = "0.21" +strum_macros = "0.21" +nanoid = "0.4.0" +chrono = { version = "0.4" } +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" +serde_repr = "0.1" diff --git a/shared-lib/flowy-folder-data-model/src/revision/app_rev.rs b/shared-lib/folder-rev-model/src/app_rev.rs similarity index 92% rename from shared-lib/flowy-folder-data-model/src/revision/app_rev.rs rename to shared-lib/folder-rev-model/src/app_rev.rs index a942acb16b..457ea78152 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/app_rev.rs +++ b/shared-lib/folder-rev-model/src/app_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::{TrashRevision, TrashTypeRevision, ViewRevision}; +use crate::{TrashRevision, TrashTypeRevision, ViewRevision}; use nanoid::nanoid; use serde::{Deserialize, Serialize}; diff --git a/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs b/shared-lib/folder-rev-model/src/folder_rev.rs similarity index 98% rename from shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs rename to shared-lib/folder-rev-model/src/folder_rev.rs index e253ad56f5..f2930ca069 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/folder_rev.rs +++ b/shared-lib/folder-rev-model/src/folder_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::{TrashRevision, WorkspaceRevision}; +use crate::{TrashRevision, WorkspaceRevision}; use serde::de::{MapAccess, Visitor}; use serde::{de, Deserialize, Deserializer, Serialize}; use std::fmt; diff --git a/shared-lib/flowy-folder-data-model/src/revision/mod.rs b/shared-lib/folder-rev-model/src/lib.rs similarity index 79% rename from shared-lib/flowy-folder-data-model/src/revision/mod.rs rename to shared-lib/folder-rev-model/src/lib.rs index a1e9d5e33e..b6a8fb7b20 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/mod.rs +++ b/shared-lib/folder-rev-model/src/lib.rs @@ -1,6 +1,10 @@ +#[macro_use] +mod macros; + mod app_rev; mod folder_rev; mod trash_rev; +pub mod user_default; mod view_rev; mod workspace_rev; diff --git a/shared-lib/flowy-folder-data-model/src/macros.rs b/shared-lib/folder-rev-model/src/macros.rs similarity index 100% rename from shared-lib/flowy-folder-data-model/src/macros.rs rename to shared-lib/folder-rev-model/src/macros.rs diff --git a/shared-lib/flowy-folder-data-model/src/revision/trash_rev.rs b/shared-lib/folder-rev-model/src/trash_rev.rs similarity index 100% rename from shared-lib/flowy-folder-data-model/src/revision/trash_rev.rs rename to shared-lib/folder-rev-model/src/trash_rev.rs diff --git a/shared-lib/flowy-folder-data-model/src/user_default.rs b/shared-lib/folder-rev-model/src/user_default.rs similarity index 91% rename from shared-lib/flowy-folder-data-model/src/user_default.rs rename to shared-lib/folder-rev-model/src/user_default.rs index bb4eacc4dc..1dbd38f358 100644 --- a/shared-lib/flowy-folder-data-model/src/user_default.rs +++ b/shared-lib/folder-rev-model/src/user_default.rs @@ -1,6 +1,6 @@ -use crate::revision::{ - gen_app_id, gen_view_id, gen_workspace_id, AppRevision, ViewDataTypeRevision, ViewLayoutTypeRevision, ViewRevision, - WorkspaceRevision, +use crate::{ + gen_app_id, gen_view_id, gen_workspace_id, AppRevision, ViewDataFormatRevision, ViewLayoutTypeRevision, + ViewRevision, WorkspaceRevision, }; use chrono::Utc; @@ -50,7 +50,7 @@ fn create_default_view(app_id: String, time: chrono::DateTime) -> ViewRevis app_id, name, desc: "".to_string(), - data_type: ViewDataTypeRevision::Text, + data_format: ViewDataFormatRevision::DeltaFormat, version: 0, belongings: vec![], modified_time: time.timestamp(), diff --git a/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs b/shared-lib/folder-rev-model/src/view_rev.rs similarity index 84% rename from shared-lib/flowy-folder-data-model/src/revision/view_rev.rs rename to shared-lib/folder-rev-model/src/view_rev.rs index e5ee15f7c9..55ed802bed 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/view_rev.rs +++ b/shared-lib/folder-rev-model/src/view_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::{TrashRevision, TrashTypeRevision}; +use crate::{TrashRevision, TrashTypeRevision}; use nanoid::nanoid; use serde::{Deserialize, Serialize}; use serde_repr::*; @@ -17,7 +17,8 @@ pub struct ViewRevision { pub desc: String, #[serde(default)] - pub data_type: ViewDataTypeRevision, + #[serde(rename = "data_type")] + pub data_format: ViewDataFormatRevision, pub version: i64, // Deprecated @@ -55,14 +56,15 @@ impl std::convert::From for TrashRevision { #[derive(Eq, PartialEq, Debug, Clone, Serialize_repr, Deserialize_repr)] #[repr(u8)] -pub enum ViewDataTypeRevision { - Text = 0, - Database = 1, +pub enum ViewDataFormatRevision { + DeltaFormat = 0, + DatabaseFormat = 1, + TreeFormat = 2, } -impl std::default::Default for ViewDataTypeRevision { +impl std::default::Default for ViewDataFormatRevision { fn default() -> Self { - ViewDataTypeRevision::Text + ViewDataFormatRevision::DeltaFormat } } diff --git a/shared-lib/flowy-folder-data-model/src/revision/workspace_rev.rs b/shared-lib/folder-rev-model/src/workspace_rev.rs similarity index 92% rename from shared-lib/flowy-folder-data-model/src/revision/workspace_rev.rs rename to shared-lib/folder-rev-model/src/workspace_rev.rs index 823bb4233f..0f042e431c 100644 --- a/shared-lib/flowy-folder-data-model/src/revision/workspace_rev.rs +++ b/shared-lib/folder-rev-model/src/workspace_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::AppRevision; +use crate::AppRevision; use nanoid::nanoid; use serde::{Deserialize, Serialize}; pub fn gen_workspace_id() -> String { diff --git a/shared-lib/flowy-grid-data-model/Cargo.toml b/shared-lib/grid-rev-model/Cargo.toml similarity index 61% rename from shared-lib/flowy-grid-data-model/Cargo.toml rename to shared-lib/grid-rev-model/Cargo.toml index 671fdbb4e6..edcee680ec 100644 --- a/shared-lib/flowy-grid-data-model/Cargo.toml +++ b/shared-lib/grid-rev-model/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "flowy-grid-data-model" +name = "grid-rev-model" version = "0.1.0" edition = "2021" @@ -13,13 +13,3 @@ serde_repr = "0.1" nanoid = "0.4.0" flowy-error-code = { path = "../flowy-error-code"} indexmap = {version = "1.9.1", features = ["serde"]} -tracing = { version = "0.1", features = ["log"] } - -[build-dependencies] -lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] } - -[features] -default = [] -backend = [] -frontend = [] -dart = ["lib-infra/dart"] \ No newline at end of file diff --git a/shared-lib/flowy-grid-data-model/src/revision/filter_rev.rs b/shared-lib/grid-rev-model/src/filter_rev.rs similarity index 55% rename from shared-lib/flowy-grid-data-model/src/revision/filter_rev.rs rename to shared-lib/grid-rev-model/src/filter_rev.rs index 7079b52229..4bf6d30de6 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/filter_rev.rs +++ b/shared-lib/grid-rev-model/src/filter_rev.rs @@ -1,9 +1,12 @@ +use crate::FieldTypeRevision; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] -pub struct FilterConfigurationRevision { +pub struct FilterRevision { pub id: String, pub field_id: String, + pub field_type_rev: FieldTypeRevision, pub condition: u8, - pub content: Option, + #[serde(default)] + pub content: String, } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs b/shared-lib/grid-rev-model/src/grid_block.rs similarity index 96% rename from shared-lib/flowy-grid-data-model/src/revision/grid_block.rs rename to shared-lib/grid-rev-model/src/grid_block.rs index 57bcf1b017..e75b2b0f37 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_block.rs +++ b/shared-lib/grid-rev-model/src/grid_block.rs @@ -64,7 +64,7 @@ impl RowChangeset { } } -#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct CellRevision { pub data: String, } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/grid-rev-model/src/grid_rev.rs similarity index 99% rename from shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs rename to shared-lib/grid-rev-model/src/grid_rev.rs index 9728de1fdb..5bf7b8c4ab 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs +++ b/shared-lib/grid-rev-model/src/grid_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::GridBlockRevision; +use crate::GridBlockRevision; use bytes::Bytes; use indexmap::IndexMap; use nanoid::nanoid; diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/grid-rev-model/src/grid_setting_rev.rs similarity index 87% rename from shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs rename to shared-lib/grid-rev-model/src/grid_setting_rev.rs index 996b5abcba..31d1b2ba47 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs +++ b/shared-lib/grid-rev-model/src/grid_setting_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::{FieldRevision, FieldTypeRevision, FilterConfigurationRevision, GroupConfigurationRevision}; +use crate::{FieldRevision, FieldTypeRevision, FilterRevision, GroupConfigurationRevision}; use indexmap::IndexMap; use nanoid::nanoid; use serde::{Deserialize, Serialize}; @@ -19,8 +19,8 @@ pub fn gen_grid_sort_id() -> String { nanoid!(6) } -pub type FilterConfiguration = Configuration; -pub type FilterConfigurationsByFieldId = HashMap>>; +pub type FilterConfiguration = Configuration; +pub type FilterConfigurationsByFieldId = HashMap>>; // pub type GroupConfiguration = Configuration; pub type GroupConfigurationsByFieldId = HashMap>>; @@ -49,7 +49,7 @@ where .get_mut(field_id) .and_then(|object_rev_map| object_rev_map.get_mut(field_type)); if value.is_none() { - tracing::warn!("[Configuration] Can't find the {:?} with", std::any::type_name::()); + eprintln!("[Configuration] Can't find the {:?} with", std::any::type_name::()); } value } @@ -60,7 +60,7 @@ where .cloned() } - pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> Option>>> { + pub fn get_objects_by_field_revs(&self, field_revs: &[Arc]) -> HashMap>> { // Get the objects according to the FieldType, so we need iterate the field_revs. let objects_by_field_id = field_revs .iter() @@ -73,11 +73,11 @@ where Some((field_rev.id.clone(), objects)) }) .collect::>>>(); - Some(objects_by_field_id) + objects_by_field_id } pub fn get_all_objects(&self) -> Vec> { - self.inner.values().map(|map| map.all_objects()).flatten().collect() + self.inner.values().flat_map(|map| map.all_objects()).collect() } /// add object to the end of the list @@ -117,7 +117,7 @@ where } pub fn all_objects(&self) -> Vec> { - self.object_by_field_type.values().cloned().flatten().collect() + self.object_by_field_type.values().flatten().cloned().collect() } } diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs b/shared-lib/grid-rev-model/src/grid_view.rs similarity index 95% rename from shared-lib/flowy-grid-data-model/src/revision/grid_view.rs rename to shared-lib/grid-rev-model/src/grid_view.rs index 06178d23c1..aa4dac5dea 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/grid_view.rs +++ b/shared-lib/grid-rev-model/src/grid_view.rs @@ -1,4 +1,4 @@ -use crate::revision::{FilterConfiguration, GroupConfiguration}; +use crate::{FilterConfiguration, GroupConfiguration}; use nanoid::nanoid; use serde::{Deserialize, Serialize}; use serde_repr::*; @@ -71,7 +71,7 @@ pub struct RowOrderRevision { #[cfg(test)] mod tests { - use crate::revision::GridViewRevision; + use crate::GridViewRevision; #[test] fn grid_view_revision_serde_test() { diff --git a/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs b/shared-lib/grid-rev-model/src/group_rev.rs similarity index 96% rename from shared-lib/flowy-grid-data-model/src/revision/group_rev.rs rename to shared-lib/grid-rev-model/src/group_rev.rs index 44a7a4e1f6..ff7940c7d5 100644 --- a/shared-lib/flowy-grid-data-model/src/revision/group_rev.rs +++ b/shared-lib/grid-rev-model/src/group_rev.rs @@ -1,4 +1,4 @@ -use crate::revision::{gen_grid_group_id, FieldTypeRevision}; +use crate::{gen_grid_group_id, FieldTypeRevision}; use serde::{Deserialize, Serialize}; use serde_json::Error; use serde_repr::*; @@ -166,7 +166,7 @@ impl std::default::Default for DateCondition { #[cfg(test)] mod tests { - use crate::revision::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision}; + use crate::{GroupConfigurationRevision, SelectOptionGroupConfigurationRevision}; #[test] fn group_configuration_serde_test() { diff --git a/shared-lib/flowy-grid-data-model/src/revision/mod.rs b/shared-lib/grid-rev-model/src/lib.rs similarity index 100% rename from shared-lib/flowy-grid-data-model/src/revision/mod.rs rename to shared-lib/grid-rev-model/src/lib.rs diff --git a/shared-lib/lib-infra/Cargo.toml b/shared-lib/lib-infra/Cargo.toml index 8b74a68a4c..4852f7932b 100644 --- a/shared-lib/lib-infra/Cargo.toml +++ b/shared-lib/lib-infra/Cargo.toml @@ -45,8 +45,11 @@ proto_gen = [ "phf", "walkdir", "console", - "toml" + "toml", + "cmd_lib", + "protoc-rust", + "walkdir", + "protoc-bin-vendored", ] -protobuf_file_gen = ["cmd_lib", "protoc-rust", "walkdir", "protoc-bin-vendored",] dart_event = ["walkdir", "flowy-ast", "tera", "syn"] dart = ["proto_gen", "dart_event"] \ No newline at end of file diff --git a/shared-lib/lib-infra/src/code_gen/mod.rs b/shared-lib/lib-infra/src/code_gen/mod.rs index d06699b862..7f01c9ef08 100644 --- a/shared-lib/lib-infra/src/code_gen/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/mod.rs @@ -1,13 +1,13 @@ -#[cfg(feature = "protobuf_file_gen")] +#[cfg(feature = "proto_gen")] pub mod protobuf_file; #[cfg(feature = "dart_event")] pub mod dart_event; -#[cfg(any(feature = "protobuf_file_gen", feature = "dart_event"))] +#[cfg(any(feature = "proto_gen", feature = "dart_event"))] mod flowy_toml; -#[cfg(any(feature = "protobuf_file_gen", feature = "dart_event"))] +#[cfg(any(feature = "proto_gen", feature = "dart_event"))] pub mod util; #[derive(serde::Serialize, serde::Deserialize)] diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/ast.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/ast.rs index 03ea485716..995764a793 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/ast.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/ast.rs @@ -22,8 +22,7 @@ pub fn parse_protobuf_context_from(crate_paths: Vec) -> Vec>(); ProtobufCrateContext::from_crate_info(crate_info, files) diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs index 5f293e5e2c..1e2d89ba2d 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/mod.rs @@ -182,9 +182,9 @@ pub fn check_pb_dart_plugin() { )); } - msg.push_str(&"✅ You can fix that by adding:".to_string()); - msg.push_str(&"\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n".to_string()); - msg.push_str(&"to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)".to_string()); + msg.push_str("✅ You can fix that by adding:"); + msg.push_str("\n\texport PATH=\"$PATH\":\"$HOME/.pub-cache/bin\"\n"); + msg.push_str("to your shell's config file.(.bashrc, .bash, .profile, .zshrc etc.)"); panic!("{}", msg) } } @@ -198,13 +198,9 @@ fn gen_proto_files(crate_name: &str, crate_path: &str) -> Vec { .map(|info| info.protobuf_crate.clone()) .collect::>(); - crate_context - .into_iter() - .map(|info| info.files) - .flatten() - .for_each(|file| { - println!("cargo:rerun-if-changed={}", file.file_path); - }); + crate_context.into_iter().flat_map(|info| info.files).for_each(|file| { + println!("cargo:rerun-if-changed={}", file.file_path); + }); proto_crates } diff --git a/shared-lib/lib-infra/src/code_gen/protobuf_file/proto_gen.rs b/shared-lib/lib-infra/src/code_gen/protobuf_file/proto_gen.rs index 25144d2c52..f38f9b40fc 100644 --- a/shared-lib/lib-infra/src/code_gen/protobuf_file/proto_gen.rs +++ b/shared-lib/lib-infra/src/code_gen/protobuf_file/proto_gen.rs @@ -52,7 +52,7 @@ impl ProtoGenerator { fn write_proto_files(crate_contexts: &[ProtobufCrateContext]) { let file_path_content_map = crate_contexts .iter() - .map(|ctx| { + .flat_map(|ctx| { ctx.files .iter() .map(|file| { @@ -66,7 +66,6 @@ fn write_proto_files(crate_contexts: &[ProtobufCrateContext]) { }) .collect::>() }) - .flatten() .collect::>(); for context in crate_contexts { @@ -152,12 +151,11 @@ impl ProtoCache { fn from_crate_contexts(crate_contexts: &[ProtobufCrateContext]) -> Self { let proto_files = crate_contexts .iter() - .map(|crate_info| &crate_info.files) - .flatten() + .flat_map(|crate_info| &crate_info.files) .collect::>(); - let structs: Vec = proto_files.iter().map(|info| info.structs.clone()).flatten().collect(); - let enums: Vec = proto_files.iter().map(|info| info.enums.clone()).flatten().collect(); + let structs: Vec = proto_files.iter().flat_map(|info| info.structs.clone()).collect(); + let enums: Vec = proto_files.iter().flat_map(|info| info.enums.clone()).collect(); Self { structs, enums } } } diff --git a/shared-lib/lib-infra/src/future.rs b/shared-lib/lib-infra/src/future.rs index a6bad3b298..9c1fe5ee51 100644 --- a/shared-lib/lib-infra/src/future.rs +++ b/shared-lib/lib-infra/src/future.rs @@ -8,20 +8,20 @@ use std::{ task::{Context, Poll}, }; -pub fn wrap_future(f: T) -> AFFuture +pub fn to_future(f: T) -> Fut where T: Future + Send + Sync + 'static, { - AFFuture { fut: Box::pin(f) } + Fut { fut: Box::pin(f) } } #[pin_project] -pub struct AFFuture { +pub struct Fut { #[pin] pub fut: Pin + Sync + Send>>, } -impl Future for AFFuture +impl Future for Fut where T: Send + Sync, { diff --git a/shared-lib/lib-infra/src/lib.rs b/shared-lib/lib-infra/src/lib.rs index f304749731..9168f97f09 100644 --- a/shared-lib/lib-infra/src/lib.rs +++ b/shared-lib/lib-infra/src/lib.rs @@ -1,4 +1,5 @@ pub mod code_gen; pub mod future; +pub mod ref_map; pub mod retry; pub mod util; diff --git a/shared-lib/lib-infra/src/ref_map.rs b/shared-lib/lib-infra/src/ref_map.rs new file mode 100644 index 0000000000..33731220fa --- /dev/null +++ b/shared-lib/lib-infra/src/ref_map.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; +use std::sync::Arc; + +pub trait RefCountValue { + fn did_remove(&self) {} +} + +struct RefCountHandler { + ref_count: usize, + inner: T, +} + +impl RefCountHandler { + pub fn new(inner: T) -> Self { + Self { ref_count: 1, inner } + } + + pub fn increase_ref_count(&mut self) { + self.ref_count += 1; + } +} + +pub struct RefCountHashMap(HashMap>); + +impl std::default::Default for RefCountHashMap { + fn default() -> Self { + Self(HashMap::new()) + } +} + +impl RefCountHashMap +where + T: Clone + Send + Sync + RefCountValue, +{ + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, key: &str) -> Option { + self.0.get(key).map(|handler| handler.inner.clone()) + } + + pub fn values(&self) -> Vec { + self.0.values().map(|value| value.inner.clone()).collect::>() + } + + pub fn insert(&mut self, key: String, value: T) { + if let Some(handler) = self.0.get_mut(&key) { + handler.increase_ref_count(); + } else { + let handler = RefCountHandler::new(value); + self.0.insert(key, handler); + } + } + + pub fn remove(&mut self, key: &str) { + let mut should_remove = false; + if let Some(value) = self.0.get_mut(key) { + if value.ref_count > 0 { + value.ref_count -= 1; + } + should_remove = value.ref_count == 0; + } + + if should_remove { + if let Some(handler) = self.0.remove(key) { + handler.inner.did_remove(); + } + } + } +} + +impl RefCountValue for Arc +where + T: RefCountValue, +{ + fn did_remove(&self) { + (**self).did_remove() + } +} diff --git a/shared-lib/lib-ot/Cargo.toml b/shared-lib/lib-ot/Cargo.toml index 64cb062ff1..c654a00235 100644 --- a/shared-lib/lib-ot/Cargo.toml +++ b/shared-lib/lib-ot/Cargo.toml @@ -8,8 +8,6 @@ edition = "2018" [dependencies] bytecount = "0.6.0" serde = { version = "1.0", features = ["derive", "rc"] } -#flowy-revision = { path = "../../frontend/rust-lib/flowy-revision" } -#protobuf = {version = "2.18.0"} tokio = { version = "1", features = ["sync"] } dashmap = "5" md5 = "0.7.0" @@ -19,6 +17,7 @@ thiserror = "1.0" serde_json = { version = "1.0" } serde_repr = { version = "0.1" } derive_more = { version = "0.99", features = ["display"] } +indexmap = {version = "1.9.1", features = ["serde"]} log = "0.4" tracing = { version = "0.1", features = ["log"] } lazy_static = "1.4.0" diff --git a/shared-lib/lib-ot/src/core/attributes/attribute.rs b/shared-lib/lib-ot/src/core/attributes/attribute.rs index cdbf7c44e1..bed2baade8 100644 --- a/shared-lib/lib-ot/src/core/attributes/attribute.rs +++ b/shared-lib/lib-ot/src/core/attributes/attribute.rs @@ -1,7 +1,7 @@ use crate::core::{OperationAttributes, OperationTransform}; use crate::errors::OTError; +use indexmap::IndexMap; use serde::{Deserialize, Serialize}; -use std::collections::HashMap; use std::fmt; use std::fmt::Display; @@ -12,7 +12,14 @@ pub struct AttributeEntry { } impl AttributeEntry { - pub fn remove_value(&mut self) { + pub fn new, V: Into>(key: K, value: V) -> Self { + Self { + key: key.into(), + value: value.into(), + } + } + + pub fn clear(&mut self) { self.value.ty = None; self.value.value = None; } @@ -27,10 +34,10 @@ impl std::convert::From for AttributeHashMap { } #[derive(Default, Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] -pub struct AttributeHashMap(HashMap); +pub struct AttributeHashMap(IndexMap); impl std::ops::Deref for AttributeHashMap { - type Target = HashMap; + type Target = IndexMap; fn deref(&self) -> &Self::Target { &self.0 @@ -45,13 +52,17 @@ impl std::ops::DerefMut for AttributeHashMap { impl AttributeHashMap { pub fn new() -> AttributeHashMap { - AttributeHashMap(HashMap::new()) + AttributeHashMap(IndexMap::new()) } - pub fn from_value(attribute_map: HashMap) -> Self { - Self(attribute_map) + pub fn into_inner(self) -> IndexMap { + self.0 } + // pub fn from_value(attribute_map: HashMap) -> Self { + // Self(attribute_map) + // } + pub fn insert>(&mut self, key: K, value: V) { self.0.insert(key.to_string(), value.into()); } @@ -104,6 +115,10 @@ impl AttributeHashMap { pub fn is_empty(&self) -> bool { self.0.is_empty() } + + pub fn to_json(&self) -> Result { + serde_json::to_string(self).map_err(|err| OTError::serde().context(err)) + } } impl Display for AttributeHashMap { @@ -199,11 +214,10 @@ impl AttributeValue { pub fn none() -> Self { Self { ty: None, value: None } } - pub fn from_int(val: usize) -> Self { - let value = if val > 0_usize { Some(val.to_string()) } else { None }; + pub fn from_int(val: i64) -> Self { Self { ty: Some(ValueType::IntType), - value, + value: Some(val.to_string()), } } @@ -215,10 +229,10 @@ impl AttributeValue { } pub fn from_bool(val: bool) -> Self { - let value = if val { Some(val.to_string()) } else { None }; + // let value = if val { Some(val.to_string()) } else { None }; Self { ty: Some(ValueType::BoolType), - value, + value: Some(val.to_string()), } } pub fn from_string(s: &str) -> Self { @@ -257,7 +271,7 @@ impl std::convert::From for AttributeValue { impl std::convert::From for AttributeValue { fn from(value: usize) -> Self { - AttributeValue::from_int(value) + AttributeValue::from_int(value as i64) } } @@ -273,6 +287,12 @@ impl std::convert::From for AttributeValue { } } +impl std::convert::From for AttributeValue { + fn from(value: f64) -> Self { + AttributeValue::from_float(value) + } +} + #[derive(Default)] pub struct AttributeBuilder { attributes: AttributeHashMap, diff --git a/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs b/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs index dd953b7ecd..80fd745343 100644 --- a/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs +++ b/shared-lib/lib-ot/src/core/attributes/attribute_serde.rs @@ -70,56 +70,70 @@ impl<'de> Deserialize<'de> for AttributeValue { where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_i16(self, value: i16) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_i32(self, value: i32) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_i64(self, value: i64) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_u8(self, value: u8) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_u16(self, value: u16) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_u32(self, value: u32) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) } fn visit_u64(self, value: u64) -> Result where E: de::Error, { - Ok(AttributeValue::from_int(value as usize)) + Ok(AttributeValue::from_int(value as i64)) + } + + fn visit_f32(self, value: f32) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_float(value as f64)) + } + + fn visit_f64(self, value: f64) -> Result + where + E: de::Error, + { + Ok(AttributeValue::from_float(value as f64)) } fn visit_str(self, s: &str) -> Result diff --git a/shared-lib/lib-ot/src/core/delta/builder.rs b/shared-lib/lib-ot/src/core/delta/builder.rs index 624be9b287..805d58580a 100644 --- a/shared-lib/lib-ot/src/core/delta/builder.rs +++ b/shared-lib/lib-ot/src/core/delta/builder.rs @@ -1,6 +1,5 @@ use crate::core::delta::operation::OperationAttributes; use crate::core::delta::{trim, DeltaOperations}; -use crate::core::DeltaOperation; /// A builder for creating new [Operations] objects. /// @@ -16,11 +15,11 @@ use crate::core::DeltaOperation; /// .build(); /// assert_eq!(delta.content().unwrap(), "AppFlowy"); /// ``` -pub struct OperationBuilder { +pub struct DeltaOperationBuilder { delta: DeltaOperations, } -impl std::default::Default for OperationBuilder +impl std::default::Default for DeltaOperationBuilder where T: OperationAttributes, { @@ -31,20 +30,21 @@ where } } -impl OperationBuilder +impl DeltaOperationBuilder where T: OperationAttributes, { pub fn new() -> Self { - OperationBuilder::default() + DeltaOperationBuilder::default() } - pub fn from_operations(operations: Vec>) -> DeltaOperations { - let mut delta = OperationBuilder::default().build(); - operations.into_iter().for_each(|operation| { - delta.add(operation); + pub fn from_delta_operation(delta_operation: DeltaOperations) -> Self { + debug_assert!(delta_operation.utf16_base_len == 0); + let mut builder = DeltaOperationBuilder::new(); + delta_operation.ops.into_iter().for_each(|operation| { + builder.delta.add(operation); }); - delta + builder } /// Retain the 'n' characters with the attributes. Use 'retain' instead if you don't @@ -52,10 +52,10 @@ where /// # Examples /// /// ``` - /// use lib_ot::text_delta::{BuildInTextAttribute, TextOperations, TextOperationBuilder}; + /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperations, DeltaTextOperationBuilder}; /// /// let mut attribute = BuildInTextAttribute::Bold(true); - /// let delta = TextOperationBuilder::new().retain_with_attributes(7, attribute.into()).build(); + /// let delta = DeltaTextOperationBuilder::new().retain_with_attributes(7, attribute.into()).build(); /// /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":true}}]"#); /// ``` @@ -111,14 +111,14 @@ where /// /// ``` /// use lib_ot::core::{OperationTransform, DeltaBuilder}; - /// use lib_ot::text_delta::{BuildInTextAttribute, TextOperationBuilder}; + /// use lib_ot::text_delta::{BuildInTextAttribute, DeltaTextOperationBuilder}; /// let delta = DeltaBuilder::new() /// .retain(3) /// .trim() /// .build(); /// assert_eq!(delta.ops.len(), 0); /// - /// let delta = TextOperationBuilder::new() + /// let delta = DeltaTextOperationBuilder::new() /// .retain_with_attributes(3, BuildInTextAttribute::Bold(true).into()) /// .trim() /// .build(); diff --git a/shared-lib/lib-ot/src/core/delta/cursor.rs b/shared-lib/lib-ot/src/core/delta/cursor.rs index de2ae4304e..42c28dae10 100644 --- a/shared-lib/lib-ot/src/core/delta/cursor.rs +++ b/shared-lib/lib-ot/src/core/delta/cursor.rs @@ -30,8 +30,8 @@ where /// /// ``` /// use lib_ot::core::{OperationsCursor, OperationIterator, Interval, DeltaOperation}; - /// use lib_ot::text_delta::TextOperations; - /// let mut delta = TextOperations::default(); + /// use lib_ot::text_delta::DeltaTextOperations; + /// let mut delta = DeltaTextOperations::default(); /// delta.add(DeltaOperation::insert("123")); /// delta.add(DeltaOperation::insert("4")); /// diff --git a/shared-lib/lib-ot/src/core/delta/iterator.rs b/shared-lib/lib-ot/src/core/delta/iterator.rs index bd4d3ee55a..c0b3aaa3cb 100644 --- a/shared-lib/lib-ot/src/core/delta/iterator.rs +++ b/shared-lib/lib-ot/src/core/delta/iterator.rs @@ -15,8 +15,8 @@ pub(crate) const MAX_IV_LEN: usize = i32::MAX as usize; /// /// ``` /// use lib_ot::core::{OperationIterator, Interval, DeltaOperation}; -/// use lib_ot::text_delta::TextOperations; -/// let mut delta = TextOperations::default(); +/// use lib_ot::text_delta::DeltaTextOperations; +/// let mut delta = DeltaTextOperations::default(); /// delta.add(DeltaOperation::insert("123")); /// delta.add(DeltaOperation::insert("4")); /// assert_eq!( diff --git a/shared-lib/lib-ot/src/core/delta/operation/builder.rs b/shared-lib/lib-ot/src/core/delta/operation/builder.rs deleted file mode 100644 index bdea0e80ae..0000000000 --- a/shared-lib/lib-ot/src/core/delta/operation/builder.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::core::delta::operation::{DeltaOperation, EmptyAttributes, OperationAttributes}; - -// pub type RichTextOpBuilder = OperationsBuilder; -pub type PlainTextOpBuilder = OperationsBuilder; - -#[derive(Default)] -pub struct OperationsBuilder { - operations: Vec>, -} - -impl OperationsBuilder -where - T: OperationAttributes, -{ - pub fn new() -> OperationsBuilder { - OperationsBuilder::default() - } - - pub fn retain_with_attributes(mut self, n: usize, attributes: T) -> OperationsBuilder { - let retain = DeltaOperation::retain_with_attributes(n, attributes); - self.operations.push(retain); - self - } - - pub fn retain(mut self, n: usize) -> OperationsBuilder { - let retain = DeltaOperation::retain(n); - self.operations.push(retain); - self - } - - pub fn delete(mut self, n: usize) -> OperationsBuilder { - self.operations.push(DeltaOperation::Delete(n)); - self - } - - pub fn insert_with_attributes(mut self, s: &str, attributes: T) -> OperationsBuilder { - let insert = DeltaOperation::insert_with_attributes(s, attributes); - self.operations.push(insert); - self - } - - pub fn insert(mut self, s: &str) -> OperationsBuilder { - let insert = DeltaOperation::insert(s); - self.operations.push(insert); - self - } - - pub fn build(self) -> Vec> { - self.operations - } -} diff --git a/shared-lib/lib-ot/src/core/delta/operation/mod.rs b/shared-lib/lib-ot/src/core/delta/operation/mod.rs index 814cb76794..067c55e98a 100644 --- a/shared-lib/lib-ot/src/core/delta/operation/mod.rs +++ b/shared-lib/lib-ot/src/core/delta/operation/mod.rs @@ -1,8 +1,6 @@ #![allow(clippy::module_inception)] -mod builder; mod operation; mod operation_serde; -pub use builder::*; pub use operation::*; pub use operation_serde::*; diff --git a/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs b/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs index ec00b03800..6d30178c91 100644 --- a/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs +++ b/shared-lib/lib-ot/src/core/delta/operation/operation_serde.rs @@ -84,7 +84,7 @@ where let map: T = map.next_value()?; attributes = Some(map); } - _ => panic!(), + _ => {} } } match operation { diff --git a/shared-lib/lib-ot/src/core/delta/ops.rs b/shared-lib/lib-ot/src/core/delta/ops.rs index df697f845e..2d41278198 100644 --- a/shared-lib/lib-ot/src/core/delta/ops.rs +++ b/shared-lib/lib-ot/src/core/delta/ops.rs @@ -1,10 +1,9 @@ -use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; - use crate::core::delta::operation::{DeltaOperation, EmptyAttributes, OperationAttributes, OperationTransform}; use crate::core::delta::{OperationIterator, MAX_IV_LEN}; use crate::core::interval::Interval; use crate::core::ot_str::OTString; -use crate::core::OperationBuilder; +use crate::core::DeltaOperationBuilder; +use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; use bytes::Bytes; use serde::de::DeserializeOwned; use std::{ @@ -15,8 +14,7 @@ use std::{ str::FromStr, }; -pub type Delta = DeltaOperations; -pub type DeltaBuilder = OperationBuilder; +pub type DeltaBuilder = DeltaOperationBuilder; /// A [Delta] contains list of operations that consists of 'Retain', 'Delete' and 'Insert' operation. /// Check out the [Operation] for more details. It describes the document as a sequence of @@ -226,6 +224,10 @@ where Ok(new_s) } + pub fn inverted(&self) -> Self { + self.invert_str("") + } + /// Computes the inverse [Delta]. The inverse of an operation is the /// operation that reverts the effects of the operation /// # Arguments @@ -571,12 +573,12 @@ where /// # Examples /// /// ``` - /// use lib_ot::core::OperationBuilder; - /// use lib_ot::text_delta::{TextOperations}; + /// use lib_ot::core::DeltaOperationBuilder; + /// use lib_ot::text_delta::{DeltaTextOperations}; /// let json = r#"[ /// {"retain":7,"attributes":{"bold":null}} /// ]"#; - /// let delta = TextOperations::from_json(json).unwrap(); + /// let delta = DeltaTextOperations::from_json(json).unwrap(); /// assert_eq!(delta.json_str(), r#"[{"retain":7,"attributes":{"bold":null}}]"#); /// ``` pub fn from_json(json: &str) -> Result { diff --git a/shared-lib/lib-ot/src/core/delta/ops_serde.rs b/shared-lib/lib-ot/src/core/delta/ops_serde.rs index 0a3ebc7230..3b7cd29087 100644 --- a/shared-lib/lib-ot/src/core/delta/ops_serde.rs +++ b/shared-lib/lib-ot/src/core/delta/ops_serde.rs @@ -1,6 +1,6 @@ use crate::core::delta::operation::OperationAttributes; use crate::core::delta::DeltaOperations; -use serde::de::DeserializeOwned; + use serde::{ de::{SeqAccess, Visitor}, ser::SerializeSeq, diff --git a/shared-lib/lib-ot/src/core/interval.rs b/shared-lib/lib-ot/src/core/interval.rs index cc907ec3ea..6bedf785e8 100644 --- a/shared-lib/lib-ot/src/core/interval.rs +++ b/shared-lib/lib-ot/src/core/interval.rs @@ -1,3 +1,4 @@ +use serde::{Deserialize, Serialize}; use std::{ cmp::{max, min}, fmt, @@ -9,7 +10,7 @@ use std::{ /// /// It is an invariant that `start <= end`. An interval where `end < start` is /// considered empty. -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct Interval { pub start: usize, pub end: usize, diff --git a/shared-lib/lib-ot/src/core/node_tree/mod.rs b/shared-lib/lib-ot/src/core/node_tree/mod.rs index 1f7205201d..417c4af03f 100644 --- a/shared-lib/lib-ot/src/core/node_tree/mod.rs +++ b/shared-lib/lib-ot/src/core/node_tree/mod.rs @@ -6,10 +6,15 @@ mod operation; mod operation_serde; mod path; mod transaction; +mod transaction_serde; mod tree; +mod tree_serde; pub use node::*; pub use operation::*; pub use path::*; pub use transaction::*; pub use tree::*; +pub use tree_serde::*; + +pub use indextree::NodeId; diff --git a/shared-lib/lib-ot/src/core/node_tree/node.rs b/shared-lib/lib-ot/src/core/node_tree/node.rs index 557078f9f6..2608912645 100644 --- a/shared-lib/lib-ot/src/core/node_tree/node.rs +++ b/shared-lib/lib-ot/src/core/node_tree/node.rs @@ -1,9 +1,9 @@ use super::node_serde::*; use crate::core::attributes::{AttributeHashMap, AttributeKey, AttributeValue}; -use crate::core::NodeBody::Delta; -use crate::core::OperationTransform; +use crate::core::Body::Delta; +use crate::core::{AttributeEntry, OperationTransform}; use crate::errors::OTError; -use crate::text_delta::TextOperations; +use crate::text_delta::DeltaTextOperations; use serde::{Deserialize, Serialize}; #[derive(Default, Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] @@ -17,9 +17,9 @@ pub struct NodeData { #[serde(serialize_with = "serialize_body")] #[serde(deserialize_with = "deserialize_body")] - #[serde(skip_serializing_if = "NodeBody::is_empty")] + #[serde(skip_serializing_if = "Body::is_empty")] #[serde(default)] - pub body: NodeBody, + pub body: Body, #[serde(skip_serializing_if = "Vec::is_empty")] #[serde(default)] @@ -45,6 +45,9 @@ impl NodeData { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RepeatedNodeData(Vec); + /// Builder for [`NodeData`] pub struct NodeDataBuilder { node: NodeData, @@ -58,7 +61,7 @@ impl NodeDataBuilder { } /// Appends a new node to the end of the builder's node children. - pub fn add_node(mut self, node: NodeData) -> Self { + pub fn add_node_data(mut self, node: NodeData) -> Self { self.node.children.push(node); self } @@ -66,14 +69,18 @@ impl NodeDataBuilder { /// Inserts attributes to the builder's node. /// /// The attributes will be replace if they shared the same key - pub fn insert_attribute(mut self, key: AttributeKey, value: AttributeValue) -> Self { - self.node.attributes.insert(key, value); + pub fn insert_attribute, V: Into>(mut self, key: K, value: V) -> Self { + self.node.attributes.insert(key.into(), value); self } - /// Inserts a body to the builder's node - pub fn insert_body(mut self, body: NodeBody) -> Self { - self.node.body = body; + pub fn insert_attribute_entry(mut self, entry: AttributeEntry) -> Self { + self.node.attributes.insert_entry(entry); + self + } + + pub fn insert_delta(mut self, delta: DeltaTextOperations) -> Self { + self.node.body = Body::Delta(delta); self } @@ -92,24 +99,24 @@ impl NodeDataBuilder { /// compose, transform and invert. /// #[derive(Debug, Clone, PartialEq, Eq)] -pub enum NodeBody { +pub enum Body { Empty, - Delta(TextOperations), + Delta(DeltaTextOperations), } -impl std::default::Default for NodeBody { +impl std::default::Default for Body { fn default() -> Self { - NodeBody::Empty + Body::Empty } } -impl NodeBody { +impl Body { fn is_empty(&self) -> bool { - matches!(self, NodeBody::Empty) + matches!(self, Body::Empty) } } -impl OperationTransform for NodeBody { +impl OperationTransform for Body { /// Only the same enum variant can perform the compose operation. fn compose(&self, other: &Self) -> Result where @@ -117,7 +124,8 @@ impl OperationTransform for NodeBody { { match (self, other) { (Delta(a), Delta(b)) => a.compose(b).map(Delta), - (NodeBody::Empty, NodeBody::Empty) => Ok(NodeBody::Empty), + (Body::Empty, Delta(b)) => Ok(Delta(b.clone())), + (Body::Empty, Body::Empty) => Ok(Body::Empty), (l, r) => { let msg = format!("{:?} can not compose {:?}", l, r); Err(OTError::internal().context(msg)) @@ -132,7 +140,7 @@ impl OperationTransform for NodeBody { { match (self, other) { (Delta(l), Delta(r)) => l.transform(r).map(|(ta, tb)| (Delta(ta), Delta(tb))), - (NodeBody::Empty, NodeBody::Empty) => Ok((NodeBody::Empty, NodeBody::Empty)), + (Body::Empty, Body::Empty) => Ok((Body::Empty, Body::Empty)), (l, r) => { let msg = format!("{:?} can not compose {:?}", l, r); Err(OTError::internal().context(msg)) @@ -144,7 +152,7 @@ impl OperationTransform for NodeBody { fn invert(&self, other: &Self) -> Self { match (self, other) { (Delta(l), Delta(r)) => Delta(l.invert(r)), - (NodeBody::Empty, NodeBody::Empty) => NodeBody::Empty, + (Body::Empty, Body::Empty) => Body::Empty, (l, r) => { tracing::error!("{:?} can not compose {:?}", l, r); l.clone() @@ -158,20 +166,75 @@ impl OperationTransform for NodeBody { /// Each NodeBody except the Empty should have its corresponding changeset variant. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] -pub enum NodeBodyChangeset { +pub enum Changeset { Delta { - delta: TextOperations, - inverted: TextOperations, + delta: DeltaTextOperations, + inverted: DeltaTextOperations, + }, + Attributes { + new: AttributeHashMap, + old: AttributeHashMap, }, } -impl NodeBodyChangeset { - pub fn inverted(&self) -> NodeBodyChangeset { +impl Changeset { + pub fn is_delta(&self) -> bool { match self { - NodeBodyChangeset::Delta { delta, inverted } => NodeBodyChangeset::Delta { + Changeset::Delta { .. } => true, + Changeset::Attributes { .. } => false, + } + } + pub fn is_attribute(&self) -> bool { + match self { + Changeset::Delta { .. } => false, + Changeset::Attributes { .. } => true, + } + } + pub fn inverted(&self) -> Changeset { + match self { + Changeset::Delta { delta, inverted } => Changeset::Delta { delta: inverted.clone(), inverted: delta.clone(), }, + Changeset::Attributes { new, old } => Changeset::Attributes { + new: old.clone(), + old: new.clone(), + }, + } + } + + pub fn compose(&mut self, other: &Changeset) -> Result<(), OTError> { + match (self, other) { + ( + Changeset::Delta { delta, inverted }, + Changeset::Delta { + delta: other_delta, + inverted: _, + }, + ) => { + let original = delta.compose(inverted)?; + let new_delta = delta.compose(other_delta)?; + let new_inverted = new_delta.invert(&original); + + *delta = new_delta; + *inverted = new_inverted; + Ok(()) + } + ( + Changeset::Attributes { new, old }, + Changeset::Attributes { + new: other_new, + old: other_old, + }, + ) => { + *new = other_new.clone(); + *old = other_old.clone(); + Ok(()) + } + (left, right) => { + let err = format!("Compose changeset failed. {:?} can't compose {:?}", left, right); + Err(OTError::compose().context(err)) + } } } } @@ -181,7 +244,7 @@ impl NodeBodyChangeset { #[derive(Clone, Eq, PartialEq, Debug)] pub struct Node { pub node_type: String, - pub body: NodeBody, + pub body: Body, pub attributes: AttributeHashMap, } @@ -190,16 +253,22 @@ impl Node { Node { node_type: node_type.into(), attributes: AttributeHashMap::new(), - body: NodeBody::Empty, + body: Body::Empty, } } - pub fn apply_body_changeset(&mut self, changeset: NodeBodyChangeset) { + pub fn apply_changeset(&mut self, changeset: Changeset) -> Result<(), OTError> { match changeset { - NodeBodyChangeset::Delta { delta, inverted: _ } => match self.body.compose(&Delta(delta)) { - Ok(new_body) => self.body = new_body, - Err(e) => tracing::error!("{:?}", e), - }, + Changeset::Delta { delta, inverted: _ } => { + let new_body = self.body.compose(&Delta(delta))?; + self.body = new_body; + Ok(()) + } + Changeset::Attributes { new, old: _ } => { + let new_attributes = AttributeHashMap::compose(&self.attributes, &new)?; + self.attributes = new_attributes; + Ok(()) + } } } } diff --git a/shared-lib/lib-ot/src/core/node_tree/node_serde.rs b/shared-lib/lib-ot/src/core/node_tree/node_serde.rs index 71c8f64b45..8e360b8937 100644 --- a/shared-lib/lib-ot/src/core/node_tree/node_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/node_serde.rs @@ -1,18 +1,18 @@ -use super::NodeBody; -use crate::text_delta::TextOperations; +use super::Body; +use crate::text_delta::DeltaTextOperations; use serde::de::{self, MapAccess, Visitor}; use serde::ser::SerializeMap; use serde::{Deserializer, Serializer}; use std::fmt; -pub fn serialize_body(body: &NodeBody, serializer: S) -> Result +pub fn serialize_body(body: &Body, serializer: S) -> Result where S: Serializer, { let mut map = serializer.serialize_map(Some(3))?; match body { - NodeBody::Empty => {} - NodeBody::Delta(delta) => { + Body::Empty => {} + Body::Delta(delta) => { map.serialize_key("delta")?; map.serialize_value(delta)?; } @@ -20,36 +20,24 @@ where map.end() } -pub fn deserialize_body<'de, D>(deserializer: D) -> Result +pub fn deserialize_body<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { struct NodeBodyVisitor(); impl<'de> Visitor<'de> for NodeBodyVisitor { - type Value = NodeBody; + type Value = Body; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("Expect NodeBody") } - fn visit_seq(self, mut seq: A) -> Result - where - A: de::SeqAccess<'de>, - { - let mut delta = TextOperations::default(); - while let Some(op) = seq.next_element()? { - delta.add(op); - } - Ok(NodeBody::Delta(delta)) - } - - #[inline] fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de>, { - let mut delta: Option = None; + let mut delta: Option = None; while let Some(key) = map.next_key()? { match key { "delta" => { @@ -65,7 +53,7 @@ where } if let Some(delta) = delta { - return Ok(NodeBody::Delta(delta)); + return Ok(Body::Delta(delta)); } Err(de::Error::missing_field("delta")) diff --git a/shared-lib/lib-ot/src/core/node_tree/operation.rs b/shared-lib/lib-ot/src/core/node_tree/operation.rs index 324cf73a6e..7314584b89 100644 --- a/shared-lib/lib-ot/src/core/node_tree/operation.rs +++ b/shared-lib/lib-ot/src/core/node_tree/operation.rs @@ -1,8 +1,8 @@ -use crate::core::attributes::AttributeHashMap; -use crate::core::{NodeBodyChangeset, NodeData, Path}; +use crate::core::{Body, Changeset, NodeData, OperationTransform, Path}; use crate::errors::OTError; + use serde::{Deserialize, Serialize}; -use std::rc::Rc; +use std::sync::Arc; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "op")] @@ -10,17 +10,8 @@ pub enum NodeOperation { #[serde(rename = "insert")] Insert { path: Path, nodes: Vec }, - #[serde(rename = "update-attribute")] - UpdateAttributes { - path: Path, - new: AttributeHashMap, - old: AttributeHashMap, - }, - - #[serde(rename = "update-body")] - // #[serde(serialize_with = "serialize_edit_body")] - // #[serde(deserialize_with = "deserialize_edit_body")] - UpdateBody { path: Path, changeset: NodeBodyChangeset }, + #[serde(rename = "update")] + Update { path: Path, changeset: Changeset }, #[serde(rename = "delete")] Delete { path: Path, nodes: Vec }, @@ -30,41 +21,103 @@ impl NodeOperation { pub fn get_path(&self) -> &Path { match self { NodeOperation::Insert { path, .. } => path, - NodeOperation::UpdateAttributes { path, .. } => path, NodeOperation::Delete { path, .. } => path, - NodeOperation::UpdateBody { path, .. } => path, + NodeOperation::Update { path, .. } => path, } } pub fn get_mut_path(&mut self) -> &mut Path { match self { NodeOperation::Insert { path, .. } => path, - NodeOperation::UpdateAttributes { path, .. } => path, NodeOperation::Delete { path, .. } => path, - NodeOperation::UpdateBody { path, .. } => path, + NodeOperation::Update { path, .. } => path, } } - pub fn invert(&self) -> NodeOperation { + pub fn is_update_delta(&self) -> bool { + match self { + NodeOperation::Insert { .. } => false, + NodeOperation::Update { path: _, changeset } => changeset.is_delta(), + NodeOperation::Delete { .. } => false, + } + } + + pub fn is_update_attribute(&self) -> bool { + match self { + NodeOperation::Insert { .. } => false, + NodeOperation::Update { path: _, changeset } => changeset.is_attribute(), + NodeOperation::Delete { .. } => false, + } + } + pub fn is_insert(&self) -> bool { + match self { + NodeOperation::Insert { .. } => true, + NodeOperation::Update { .. } => false, + NodeOperation::Delete { .. } => false, + } + } + pub fn can_compose(&self, other: &NodeOperation) -> bool { + if self.get_path() != other.get_path() { + return false; + } + if self.is_update_delta() && other.is_update_delta() { + return true; + } + + if self.is_update_attribute() && other.is_update_attribute() { + return true; + } + + if self.is_insert() && other.is_update_delta() { + return true; + } + false + } + + pub fn compose(&mut self, other: &NodeOperation) -> Result<(), OTError> { + match (self, other) { + ( + NodeOperation::Insert { path: _, nodes }, + NodeOperation::Update { + path: _other_path, + changeset, + }, + ) => { + match changeset { + Changeset::Delta { delta, inverted: _ } => { + if let Body::Delta(old_delta) = &mut nodes.last_mut().unwrap().body { + let new_delta = old_delta.compose(delta)?; + *old_delta = new_delta; + } + } + Changeset::Attributes { new: _, old: _ } => { + return Err(OTError::compose().context("Can't compose the attributes changeset")); + } + } + Ok(()) + } + ( + NodeOperation::Update { path: _, changeset }, + NodeOperation::Update { + path: _, + changeset: other_changeset, + }, + ) => changeset.compose(other_changeset), + (_left, _right) => Err(OTError::compose().context("Can't compose the operation")), + } + } + + pub fn inverted(&self) -> NodeOperation { match self { NodeOperation::Insert { path, nodes } => NodeOperation::Delete { path: path.clone(), nodes: nodes.clone(), }, - NodeOperation::UpdateAttributes { - path, - new: attributes, - old: old_attributes, - } => NodeOperation::UpdateAttributes { - path: path.clone(), - new: old_attributes.clone(), - old: attributes.clone(), - }, NodeOperation::Delete { path, nodes } => NodeOperation::Insert { path: path.clone(), nodes: nodes.clone(), }, - NodeOperation::UpdateBody { path, changeset: body } => NodeOperation::UpdateBody { + NodeOperation::Update { path, changeset: body } => NodeOperation::Update { path: path.clone(), changeset: body.inverted(), }, @@ -101,7 +154,7 @@ impl NodeOperation { /// /// op_1.transform(&mut op_2); /// assert_eq!(serde_json::to_string(&op_2).unwrap(), r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); - /// + /// assert_eq!(serde_json::to_string(&op_1).unwrap(), r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text_1"}]}"#); /// ``` pub fn transform(&self, other: &mut NodeOperation) { match self { @@ -120,46 +173,24 @@ impl NodeOperation { } } -#[derive(Debug, Clone, Serialize, Deserialize, Default)] +type OperationIndexMap = Vec>; + +#[derive(Debug, Clone, Default)] pub struct NodeOperations { - operations: Vec>, + inner: OperationIndexMap, } impl NodeOperations { - pub fn into_inner(self) -> Vec> { - self.operations + pub fn new() -> Self { + Self::default() } - pub fn add_op(&mut self, operation: NodeOperation) { - self.operations.push(Rc::new(operation)); - } -} - -impl std::ops::Deref for NodeOperations { - type Target = Vec>; - - fn deref(&self) -> &Self::Target { - &self.operations - } -} - -impl std::ops::DerefMut for NodeOperations { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.operations - } -} - -impl std::convert::From> for NodeOperations { - fn from(operations: Vec) -> Self { - Self::new(operations) - } -} - -impl NodeOperations { - pub fn new(operations: Vec) -> Self { - Self { - operations: operations.into_iter().map(Rc::new).collect(), + pub fn from_operations(operations: Vec) -> Self { + let mut ops = Self::new(); + for op in operations { + ops.push_op(op) } + ops } pub fn from_bytes(bytes: Vec) -> Result { @@ -171,4 +202,59 @@ impl NodeOperations { let bytes = serde_json::to_vec(self).map_err(|err| OTError::serde().context(err))?; Ok(bytes) } + + pub fn values(&self) -> &Vec> { + &self.inner + } + + pub fn values_mut(&mut self) -> &mut Vec> { + &mut self.inner + } + + pub fn len(&self) -> usize { + self.values().len() + } + + pub fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + pub fn into_inner(self) -> Vec> { + self.inner + } + + pub fn push_op>>(&mut self, other: T) { + let other = other.into(); + if let Some(last_operation) = self.inner.last_mut() { + if last_operation.can_compose(&other) { + let mut_operation = Arc::make_mut(last_operation); + if mut_operation.compose(&other).is_ok() { + return; + } + } + } + + // If the passed-in operation can't be composed, then append it to the end. + self.inner.push(other); + } + + pub fn compose(&mut self, other: NodeOperations) { + for operation in other.values() { + self.push_op(operation.clone()); + } + } + + pub fn inverted(&self) -> Self { + let mut operations = Self::new(); + for operation in self.values() { + operations.push_op(operation.inverted()); + } + operations + } +} + +impl std::convert::From> for NodeOperations { + fn from(operations: Vec) -> Self { + Self::from_operations(operations) + } } diff --git a/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs b/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs index d6dc2c49b7..5bb5c48e29 100644 --- a/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs +++ b/shared-lib/lib-ot/src/core/node_tree/operation_serde.rs @@ -1,127 +1,49 @@ -use crate::core::{NodeBodyChangeset, Path}; -use crate::text_delta::TextOperations; -use serde::de::{self, MapAccess, Visitor}; -use serde::ser::SerializeMap; -use serde::{Deserializer, Serializer}; -use std::convert::TryInto; +use crate::core::{NodeOperation, NodeOperations}; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; -use std::marker::PhantomData; -#[allow(dead_code)] -pub fn serialize_edit_body(path: &Path, changeset: &NodeBodyChangeset, serializer: S) -> Result -where - S: Serializer, -{ - let mut map = serializer.serialize_map(Some(3))?; - map.serialize_key("path")?; - map.serialize_value(path)?; - - match changeset { - NodeBodyChangeset::Delta { delta, inverted } => { - map.serialize_key("delta")?; - map.serialize_value(delta)?; - map.serialize_key("inverted")?; - map.serialize_value(inverted)?; - map.end() +impl Serialize for NodeOperations { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let operations = self.values(); + let mut seq = serializer.serialize_seq(Some(operations.len()))?; + for operation in operations { + let _ = seq.serialize_element(&operation)?; } + seq.end() } } -#[allow(dead_code)] -pub fn deserialize_edit_body<'de, D>(deserializer: D) -> Result<(Path, NodeBodyChangeset), D::Error> -where - D: Deserializer<'de>, -{ - struct NodeBodyChangesetVisitor(); +impl<'de> Deserialize<'de> for NodeOperations { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NodeOperationsVisitor(); - impl<'de> Visitor<'de> for NodeBodyChangesetVisitor { - type Value = (Path, NodeBodyChangeset); + impl<'de> Visitor<'de> for NodeOperationsVisitor { + type Value = NodeOperations; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect Path and NodeBodyChangeset") - } + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expected node operation") + } - #[inline] - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut path: Option = None; - let mut delta_changeset = DeltaBodyChangeset::::new(); - while let Some(key) = map.next_key()? { - match key { - "delta" => { - if delta_changeset.delta.is_some() { - return Err(de::Error::duplicate_field("delta")); - } - delta_changeset.delta = Some(map.next_value()?); - } - "inverted" => { - if delta_changeset.inverted.is_some() { - return Err(de::Error::duplicate_field("inverted")); - } - delta_changeset.inverted = Some(map.next_value()?); - } - "path" => { - if path.is_some() { - return Err(de::Error::duplicate_field("path")); - } - - path = Some(map.next_value::()?) - } - other => { - panic!("Unexpected key: {}", other); - } + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut operations = NodeOperations::new(); + while let Some(operation) = seq.next_element::()? { + operations.push_op(operation); } + Ok(operations) } - if path.is_none() { - return Err(de::Error::missing_field("path")); - } - - let changeset = delta_changeset.try_into()?; - - Ok((path.unwrap(), changeset)) } - } - deserializer.deserialize_any(NodeBodyChangesetVisitor()) -} -#[allow(dead_code)] -struct DeltaBodyChangeset { - delta: Option, - inverted: Option, - error: PhantomData, -} - -impl DeltaBodyChangeset { - fn new() -> Self { - Self { - delta: None, - inverted: None, - error: PhantomData, - } - } -} - -impl std::convert::TryInto for DeltaBodyChangeset -where - E: de::Error, -{ - type Error = E; - - fn try_into(self) -> Result { - if self.delta.is_none() { - return Err(de::Error::missing_field("delta")); - } - - if self.inverted.is_none() { - return Err(de::Error::missing_field("inverted")); - } - let changeset = NodeBodyChangeset::Delta { - delta: self.delta.unwrap(), - inverted: self.inverted.unwrap(), - }; - - Ok(changeset) + deserializer.deserialize_any(NodeOperationsVisitor()) } } diff --git a/shared-lib/lib-ot/src/core/node_tree/path.rs b/shared-lib/lib-ot/src/core/node_tree/path.rs index cf7ae647ed..b754baa70f 100644 --- a/shared-lib/lib-ot/src/core/node_tree/path.rs +++ b/shared-lib/lib-ot/src/core/node_tree/path.rs @@ -23,9 +23,22 @@ use serde::{Deserialize, Serialize}; /// The path of Node A-1 will be [0,0] /// The path of Node A-2 will be [0,1] /// The path of Node B-2 will be [1,1] -#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug)] +#[derive(Clone, Serialize, Deserialize, Eq, PartialEq, Debug, Default, Hash)] pub struct Path(pub Vec); +impl Path { + pub fn is_valid(&self) -> bool { + if self.is_empty() { + return false; + } + true + } + + pub fn is_root(&self) -> bool { + self.0.len() == 1 && self.0[0] == 0 + } +} + impl std::ops::Deref for Path { type Target = Vec; diff --git a/shared-lib/lib-ot/src/core/node_tree/transaction.rs b/shared-lib/lib-ot/src/core/node_tree/transaction.rs index e6cf72e59e..70c017c218 100644 --- a/shared-lib/lib-ot/src/core/node_tree/transaction.rs +++ b/shared-lib/lib-ot/src/core/node_tree/transaction.rs @@ -1,14 +1,17 @@ -use crate::core::attributes::AttributeHashMap; +use super::{Changeset, NodeOperations}; use crate::core::{NodeData, NodeOperation, NodeTree, Path}; use crate::errors::OTError; use indextree::NodeId; -use std::rc::Rc; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; -use super::{NodeBodyChangeset, NodeOperations}; - -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct Transaction { - operations: NodeOperations, + pub operations: NodeOperations, + + #[serde(default)] + #[serde(skip_serializing_if = "Extension::is_empty")] + pub extension: Extension, } impl Transaction { @@ -19,62 +22,114 @@ impl Transaction { pub fn from_operations>(operations: T) -> Self { Self { operations: operations.into(), + extension: Extension::Empty, } } - pub fn into_operations(self) -> Vec> { + pub fn from_bytes(bytes: &[u8]) -> Result { + let transaction = serde_json::from_slice(bytes).map_err(|err| OTError::serde().context(err))?; + Ok(transaction) + } + + pub fn to_bytes(&self) -> Result, OTError> { + let bytes = serde_json::to_vec(&self).map_err(|err| OTError::serde().context(err))?; + Ok(bytes) + } + + pub fn from_json(s: &str) -> Result { + let serde_transaction: Transaction = serde_json::from_str(s).map_err(|err| OTError::serde().context(err))?; + let mut transaction = Self::new(); + transaction.extension = serde_transaction.extension; + for operation in serde_transaction.operations.into_inner() { + transaction.operations.push_op(operation); + } + Ok(transaction) + } + + pub fn to_json(&self) -> Result { + serde_json::to_string(&self).map_err(|err| OTError::serde().context(err)) + } + + pub fn into_operations(self) -> Vec> { self.operations.into_inner() } + pub fn split(self) -> (Vec>, Extension) { + (self.operations.into_inner(), self.extension) + } + + pub fn push_operation>(&mut self, operation: T) { + let operation = operation.into(); + self.operations.push_op(operation); + } + /// Make the `other` can be applied to the version after applying the `self` transaction. /// /// The semantics of transform is used when editing conflicts occur, which is often determined by the version id。 /// the operations of the transaction will be transformed into the conflict operations. pub fn transform(&self, other: &Transaction) -> Result { - let mut new_transaction = other.clone(); - for other_operation in new_transaction.iter_mut() { - let other_operation = Rc::make_mut(other_operation); - for operation in self.operations.iter() { + let mut other = other.clone(); + other.extension = self.extension.clone(); + + for other_operation in other.operations.values_mut() { + let other_operation = Arc::make_mut(other_operation); + for operation in self.operations.values() { operation.transform(other_operation); } } - Ok(new_transaction) + + Ok(other) } - pub fn compose(&mut self, other: &Transaction) -> Result<(), OTError> { + pub fn compose(&mut self, other: Transaction) -> Result<(), OTError> { // For the moment, just append `other` operations to the end of `self`. - for operation in other.operations.iter() { - self.operations.push(operation.clone()); - } + let Transaction { operations, extension } = other; + self.operations.compose(operations); + self.extension = extension; Ok(()) } } -impl std::ops::Deref for Transaction { - type Target = Vec>; +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum Extension { + Empty, + TextSelection { + before_selection: Selection, + after_selection: Selection, + }, +} - fn deref(&self) -> &Self::Target { - &self.operations +impl std::default::Default for Extension { + fn default() -> Self { + Extension::Empty } } -impl std::ops::DerefMut for Transaction { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.operations +impl Extension { + fn is_empty(&self) -> bool { + matches!(self, Extension::Empty) } } -pub struct TransactionBuilder<'a> { - node_tree: &'a NodeTree, +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Selection { + start: Position, + end: Position, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +pub struct Position { + path: Path, + offset: usize, +} +#[derive(Default)] +pub struct TransactionBuilder { operations: NodeOperations, } -impl<'a> TransactionBuilder<'a> { - pub fn new(node_tree: &'a NodeTree) -> TransactionBuilder { - TransactionBuilder { - node_tree, - operations: NodeOperations::default(), - } +impl TransactionBuilder { + pub fn new() -> TransactionBuilder { + Self::default() } /// @@ -87,17 +142,15 @@ impl<'a> TransactionBuilder<'a> { /// # Examples /// /// ``` - /// // -- 0 (root) - /// // 0 -- text_1 - /// // 1 -- text_2 + /// // 0 -- text_1 /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; - /// let mut node_tree = NodeTree::new("root"); - /// let transaction = TransactionBuilder::new(&node_tree) - /// .insert_nodes_at_path(0,vec![ NodeData::new("text_1"), NodeData::new("text_2")]) - /// .finalize(); + /// let mut node_tree = NodeTree::default(); + /// let transaction = TransactionBuilder::new() + /// .insert_nodes_at_path(0,vec![ NodeData::new("text_1")]) + /// .build(); /// node_tree.apply_transaction(transaction).unwrap(); /// - /// node_tree.node_id_at_path(vec![0, 0]); + /// node_tree.node_id_at_path(vec![0]).unwrap(); /// ``` /// pub fn insert_nodes_at_path>(self, path: T, nodes: Vec) -> Self { @@ -121,10 +174,10 @@ impl<'a> TransactionBuilder<'a> { /// // -- 0 /// // |-- text /// use lib_ot::core::{NodeTree, NodeData, TransactionBuilder}; - /// let mut node_tree = NodeTree::new("root"); - /// let transaction = TransactionBuilder::new(&node_tree) + /// let mut node_tree = NodeTree::default(); + /// let transaction = TransactionBuilder::new() /// .insert_node_at_path(0, NodeData::new("text")) - /// .finalize(); + /// .build(); /// node_tree.apply_transaction(transaction).unwrap(); /// ``` /// @@ -132,66 +185,74 @@ impl<'a> TransactionBuilder<'a> { self.insert_nodes_at_path(path, vec![node]) } - pub fn update_attributes_at_path(mut self, path: &Path, attributes: AttributeHashMap) -> Self { - match self.node_tree.get_node_at_path(path) { - Some(node) => { - let mut old_attributes = AttributeHashMap::new(); - for key in attributes.keys() { - let old_attrs = &node.attributes; - if let Some(value) = old_attrs.get(key.as_str()) { - old_attributes.insert(key.clone(), value.clone()); - } - } - - self.operations.add_op(NodeOperation::UpdateAttributes { - path: path.clone(), - new: attributes, - old: old_attributes, - }); - } - None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path), - } + pub fn update_node_at_path>(mut self, path: T, changeset: Changeset) -> Self { + self.operations.push_op(NodeOperation::Update { + path: path.into(), + changeset, + }); self } + // + // pub fn update_delta_at_path>( + // mut self, + // path: T, + // new_delta: DeltaTextOperations, + // ) -> Result { + // let path = path.into(); + // let operation: NodeOperation = self + // .operations + // .get(&path) + // .ok_or(Err(OTError::record_not_found().context("Can't found the node")))?; + // + // match operation { + // NodeOperation::Insert { path, nodes } => {} + // NodeOperation::Update { path, changeset } => {} + // NodeOperation::Delete { .. } => {} + // } + // + // match node.body { + // Body::Empty => Ok(self), + // Body::Delta(delta) => { + // let inverted = new_delta.invert(&delta); + // let changeset = Changeset::Delta { + // delta: new_delta, + // inverted, + // }; + // Ok(self.update_node_at_path(path, changeset)) + // } + // } + // } - pub fn update_body_at_path(mut self, path: &Path, changeset: NodeBodyChangeset) -> Self { - match self.node_tree.node_id_at_path(path) { - Some(_) => { - self.operations.add_op(NodeOperation::UpdateBody { - path: path.clone(), - changeset, - }); - } - None => tracing::warn!("Update attributes at path: {:?} failed. Node is not exist", path), + pub fn delete_node_at_path(self, node_tree: &NodeTree, path: &Path) -> Self { + self.delete_nodes_at_path(node_tree, path, 1) + } + + pub fn delete_nodes_at_path(mut self, node_tree: &NodeTree, path: &Path, length: usize) -> Self { + let node_id = node_tree.node_id_at_path(path); + if node_id.is_none() { + tracing::warn!("Path: {:?} doesn't contains any nodes", path); + return self; } - self - } - pub fn delete_node_at_path(self, path: &Path) -> Self { - self.delete_nodes_at_path(path, 1) - } - - pub fn delete_nodes_at_path(mut self, path: &Path, length: usize) -> Self { - let mut node = self.node_tree.node_id_at_path(path).unwrap(); + let mut node_id = node_id.unwrap(); let mut deleted_nodes = vec![]; for _ in 0..length { - deleted_nodes.push(self.get_deleted_nodes(node)); - node = self.node_tree.following_siblings(node).next().unwrap(); + deleted_nodes.push(self.get_deleted_node_data(node_tree, node_id)); + node_id = node_tree.following_siblings(node_id).next().unwrap(); } - self.operations.add_op(NodeOperation::Delete { + self.operations.push_op(NodeOperation::Delete { path: path.clone(), nodes: deleted_nodes, }); self } - fn get_deleted_nodes(&self, node_id: NodeId) -> NodeData { - let node_data = self.node_tree.get_node(node_id).unwrap(); - + fn get_deleted_node_data(&self, node_tree: &NodeTree, node_id: NodeId) -> NodeData { + let node_data = node_tree.get_node(node_id).unwrap(); let mut children = vec![]; - self.node_tree.children_from_node(node_id).for_each(|child_id| { - children.push(self.get_deleted_nodes(child_id)); + node_tree.get_children_ids(node_id).into_iter().for_each(|child_id| { + children.push(self.get_deleted_node_data(node_tree, child_id)); }); NodeData { @@ -203,11 +264,11 @@ impl<'a> TransactionBuilder<'a> { } pub fn push(mut self, op: NodeOperation) -> Self { - self.operations.add_op(op); + self.operations.push_op(op); self } - pub fn finalize(self) -> Transaction { + pub fn build(self) -> Transaction { Transaction::from_operations(self.operations) } } diff --git a/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs b/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs new file mode 100644 index 0000000000..5afa20e920 --- /dev/null +++ b/shared-lib/lib-ot/src/core/node_tree/transaction_serde.rs @@ -0,0 +1,29 @@ +use crate::core::Extension; +use serde::ser::SerializeMap; +use serde::Serializer; + +#[allow(dead_code)] +pub fn serialize_extension(extension: &Extension, serializer: S) -> Result +where + S: Serializer, +{ + match extension { + Extension::Empty => { + let map = serializer.serialize_map(None)?; + map.end() + } + Extension::TextSelection { + before_selection, + after_selection, + } => { + let mut map = serializer.serialize_map(Some(2))?; + map.serialize_key("before_selection")?; + map.serialize_value(before_selection)?; + + map.serialize_key("after_selection")?; + map.serialize_value(after_selection)?; + + map.end() + } + } +} diff --git a/shared-lib/lib-ot/src/core/node_tree/tree.rs b/shared-lib/lib-ot/src/core/node_tree/tree.rs index 50a3e50226..16399211ea 100644 --- a/shared-lib/lib-ot/src/core/node_tree/tree.rs +++ b/shared-lib/lib-ot/src/core/node_tree/tree.rs @@ -1,51 +1,141 @@ -use crate::core::attributes::AttributeHashMap; -use crate::core::{Node, NodeBodyChangeset, NodeData, NodeOperation, OperationTransform, Path, Transaction}; -use crate::errors::{ErrorBuilder, OTError, OTErrorCode}; -use indextree::{Arena, Children, FollowingSiblings, NodeId}; -use std::rc::Rc; - use super::NodeOperations; +use crate::core::{Changeset, Node, NodeData, NodeOperation, Path, Transaction}; +use crate::errors::{OTError, OTErrorCode}; +use indextree::{Arena, FollowingSiblings, NodeId}; +use std::sync::Arc; -/// +#[derive(Default, Debug)] +pub struct NodeTreeContext {} + +#[derive(Debug)] pub struct NodeTree { arena: Arena, root: NodeId, + pub context: NodeTreeContext, } impl Default for NodeTree { fn default() -> Self { - Self::new("root") + Self::new(NodeTreeContext::default()) } } +pub const PLACEHOLDER_NODE_TYPE: &str = ""; + impl NodeTree { - pub fn new(root_name: &str) -> NodeTree { + pub fn new(context: NodeTreeContext) -> NodeTree { let mut arena = Arena::new(); - let root = arena.new_node(Node::new(root_name)); - NodeTree { arena, root } + let root = arena.new_node(Node::new("root")); + NodeTree { arena, root, context } } - pub fn from_bytes(root_name: &str, bytes: Vec) -> Result { - let operations = NodeOperations::from_bytes(bytes)?; - Self::from_operations(root_name, operations) + pub fn from_node_data(node_data: NodeData, context: NodeTreeContext) -> Result { + let mut tree = Self::new(context); + let _ = tree.insert_nodes(&0_usize.into(), vec![node_data])?; + Ok(tree) } - pub fn from_operations(root_name: &str, operations: NodeOperations) -> Result { - let mut node_tree = NodeTree::new(root_name); - for operation in operations.into_inner().into_iter() { + pub fn from_bytes(bytes: &[u8]) -> Result { + let tree: NodeTree = serde_json::from_slice(bytes).map_err(|e| OTError::serde().context(e))?; + Ok(tree) + } + + pub fn to_bytes(&self) -> Vec { + match serde_json::to_vec(self) { + Ok(bytes) => bytes, + Err(e) => { + tracing::error!("{}", e); + vec![] + } + } + } + + pub fn from_operations>(operations: T, context: NodeTreeContext) -> Result { + let operations = operations.into(); + let mut node_tree = NodeTree::new(context); + for (_, operation) in operations.into_inner().into_iter().enumerate() { let _ = node_tree.apply_op(operation)?; } Ok(node_tree) } + pub fn from_transaction>(transaction: T, context: NodeTreeContext) -> Result { + let transaction = transaction.into(); + let mut tree = Self::new(context); + let _ = tree.apply_transaction(transaction)?; + Ok(tree) + } + pub fn get_node(&self, node_id: NodeId) -> Option<&Node> { + if node_id.is_removed(&self.arena) { + return None; + } Some(self.arena.get(node_id)?.get()) } pub fn get_node_at_path(&self, path: &Path) -> Option<&Node> { - { - let node_id = self.node_id_at_path(path)?; - self.get_node(node_id) + let node_id = self.node_id_at_path(path)?; + self.get_node(node_id) + } + + pub fn get_node_data_at_path(&self, path: &Path) -> Option { + let node_id = self.node_id_at_path(path)?; + let node_data = self.get_node_data(node_id)?; + Some(node_data) + } + + pub fn get_node_data_at_root(&self) -> Option { + self.get_node_data(self.root) + } + + pub fn get_node_data(&self, node_id: NodeId) -> Option { + let Node { + node_type, + body, + attributes, + } = self.get_node(node_id)?.clone(); + let mut node_data = NodeData::new(node_type); + for (key, value) in attributes.into_inner() { + node_data.attributes.insert(key, value); + } + node_data.body = body; + + let children = self.get_children_ids(node_id); + for child in children.into_iter() { + if let Some(child_node_data) = self.get_node_data(child) { + node_data.children.push(child_node_data); + } + } + Some(node_data) + } + + pub fn root_node_id(&self) -> NodeId { + self.root + } + + pub fn get_children(&self, node_id: NodeId) -> Vec<&Node> { + node_id + .children(&self.arena) + .flat_map(|node_id| self.get_node(node_id)) + .collect() + } + /// Returns a iterator used to iterate over the node ids whose parent node id is node_id + /// + /// * `node_id`: the children's parent node id + /// + pub fn get_children_ids(&self, node_id: NodeId) -> Vec { + node_id.children(&self.arena).collect() + } + + /// Serialize the node to JSON with node_id + pub fn serialize_node(&self, node_id: NodeId, pretty_json: bool) -> Result { + let node_data = self + .get_node_data(node_id) + .ok_or_else(|| OTError::internal().context("Node doesn't exist exist"))?; + if pretty_json { + serde_json::to_string_pretty(&node_data).map_err(|err| OTError::serde().context(err)) + } else { + serde_json::to_string(&node_data).map_err(|err| OTError::serde().context(err)) } } @@ -53,29 +143,33 @@ impl NodeTree { /// # Examples /// /// ``` - /// use std::rc::Rc; + /// use std::sync::Arc; /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; /// let nodes = vec![NodeData::new("text".to_string())]; /// let root_path: Path = vec![0].into(); /// let op = NodeOperation::Insert {path: root_path.clone(),nodes }; /// - /// let mut node_tree = NodeTree::new("root"); - /// node_tree.apply_op(Rc::new(op)).unwrap(); + /// let mut node_tree = NodeTree::default(); + /// node_tree.apply_op(Arc::new(op)).unwrap(); /// let node_id = node_tree.node_id_at_path(&root_path).unwrap(); /// let node_path = node_tree.path_from_node_id(node_id); /// debug_assert_eq!(node_path, root_path); /// ``` pub fn node_id_at_path>(&self, path: T) -> Option { let path = path.into(); - if path.is_empty() { - return Some(self.root); + if !path.is_valid() { + return None; } - let mut iterate_node = self.root; + let mut node_id = self.root; for id in path.iter() { - iterate_node = self.child_from_node_at_index(iterate_node, *id)?; + node_id = self.node_id_from_parent_at_index(node_id, *id)?; } - Some(iterate_node) + + if node_id.is_removed(&self.arena) { + return None; + } + Some(node_id) } pub fn path_from_node_id(&self, node_id: NodeId) -> Path { @@ -105,7 +199,7 @@ impl NodeTree { counter } - /// Returns the note_id at the position of the tree with id note_id + /// Returns the note_id at the index of the tree which its id is note_id /// # Arguments /// /// * `node_id`: the node id of the child's parent @@ -116,19 +210,19 @@ impl NodeTree { /// # Examples /// /// ``` - /// use std::rc::Rc; + /// use std::sync::Arc; /// use lib_ot::core::{NodeOperation, NodeTree, NodeData, Path}; /// let node_1 = NodeData::new("text".to_string()); /// let inserted_path: Path = vec![0].into(); /// - /// let mut node_tree = NodeTree::new("root"); + /// let mut node_tree = NodeTree::default(); /// let op = NodeOperation::Insert {path: inserted_path.clone(),nodes: vec![node_1.clone()] }; - /// node_tree.apply_op(Rc::new(op)).unwrap(); + /// node_tree.apply_op(Arc::new(op)).unwrap(); /// /// let node_2 = node_tree.get_node_at_path(&inserted_path).unwrap(); /// assert_eq!(node_2.node_type, node_1.node_type); /// ``` - pub fn child_from_node_at_index(&self, node_id: NodeId, index: usize) -> Option { + pub fn node_id_from_parent_at_index(&self, node_id: NodeId, index: usize) -> Option { let children = node_id.children(&self.arena); for (counter, child) in children.enumerate() { if counter == index { @@ -139,14 +233,6 @@ impl NodeTree { None } - /// Returns all children whose parent node id is node_id - /// - /// * `node_id`: the children's parent node id - /// - pub fn children_from_node(&self, node_id: NodeId) -> Children<'_, Node> { - node_id.children(&self.arena) - } - /// /// # Arguments /// @@ -166,56 +252,123 @@ impl NodeTree { } pub fn apply_transaction(&mut self, transaction: Transaction) -> Result<(), OTError> { - let operations = transaction.into_operations(); + let operations = transaction.split().0; for operation in operations { self.apply_op(operation)?; } + Ok(()) } - pub fn apply_op(&mut self, op: Rc) -> Result<(), OTError> { - let op = match Rc::try_unwrap(op) { + pub fn apply_op(&mut self, op: Arc) -> Result<(), OTError> { + let op = match Arc::try_unwrap(op) { Ok(op) => op, Err(op) => op.as_ref().clone(), }; match op { NodeOperation::Insert { path, nodes } => self.insert_nodes(&path, nodes), - NodeOperation::UpdateAttributes { path, new, .. } => self.update_attributes(&path, new), - NodeOperation::UpdateBody { path, changeset } => self.update_body(&path, changeset), - NodeOperation::Delete { path, nodes } => self.delete_node(&path, nodes), + NodeOperation::Update { path, changeset } => self.update(&path, changeset), + NodeOperation::Delete { path, nodes: _ } => self.delete_node(&path), } } /// Inserts nodes at given path + /// root + /// 0 - A + /// 0 - A1 + /// 1 - B + /// 0 - B1 + /// 1 - B2 + /// + /// The path of each node will be: + /// A: [0] + /// A1: [0,0] + /// B: [1] + /// B1: [1,0] + /// B2: [1,1] + /// + /// When inserting multiple nodes into the same path, each of them will be appended to the root + /// node. For example. The path is [0] and the nodes are [A, B, C]. After inserting the nodes, + /// the tree will be: + /// root + /// 0: A + /// 1: B + /// 2: C /// /// returns error if the path is empty /// fn insert_nodes(&mut self, path: &Path, nodes: Vec) -> Result<(), OTError> { - debug_assert!(!path.is_empty()); - if path.is_empty() { - return Err(OTErrorCode::PathIsEmpty.into()); + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); } let (parent_path, last_path) = path.split_at(path.0.len() - 1); let last_index = *last_path.first().unwrap(); - let parent_node = self - .node_id_at_path(parent_path) - .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; + if parent_path.is_empty() { + self.insert_nodes_at_index(self.root, last_index, nodes) + } else { + let parent_node = match self.node_id_at_path(parent_path) { + None => self.create_adjacent_nodes_for_path(parent_path), + Some(parent_node) => parent_node, + }; - self.insert_nodes_at_index(parent_node, last_index, nodes) + self.insert_nodes_at_index(parent_node, last_index, nodes) + } + } + + /// Create the adjacent nodes for the path + /// + /// It will create a corresponding node for each node on the path if it's not existing. + /// If the path is not start from zero, it will create its siblings. + /// + /// Check out the operation_insert_test.rs for more examples. + /// * operation_insert_node_when_its_parent_is_not_exist + /// * operation_insert_node_when_multiple_parent_is_not_exist_test + /// + /// # Arguments + /// + /// * `path`: creates nodes for this path + /// + /// returns: NodeId + /// + fn create_adjacent_nodes_for_path>(&mut self, path: T) -> NodeId { + let path = path.into(); + let mut node_id = self.root; + for id in path.iter() { + match self.node_id_from_parent_at_index(node_id, *id) { + None => { + let num_of_children = node_id.children(&self.arena).count(); + if *id > num_of_children { + for _ in 0..(*id - num_of_children) { + let node: Node = placeholder_node().into(); + let sibling_node = self.arena.new_node(node); + node_id.append(sibling_node, &mut self.arena); + } + } + + let node: Node = placeholder_node().into(); + let new_node_id = self.arena.new_node(node); + node_id.append(new_node_id, &mut self.arena); + node_id = new_node_id; + } + Some(next_node_id) => { + node_id = next_node_id; + } + } + } + node_id } /// Inserts nodes before the node with node_id /// fn insert_nodes_before(&mut self, node_id: &NodeId, nodes: Vec) { + if node_id.is_removed(&self.arena) { + tracing::warn!("Node:{:?} is remove before insert", node_id); + return; + } for node in nodes { let (node, children) = node.split(); let new_node_id = self.arena.new_node(node); - if node_id.is_removed(&self.arena) { - tracing::warn!("Node:{:?} is remove before insert", node_id); - return; - } - node_id.insert_before(new_node_id, &mut self.arena); self.append_nodes(&new_node_id, children); } @@ -229,14 +382,21 @@ impl NodeTree { // Append the node to the end of the children list if index greater or equal to the // length of the children. - if index >= parent.children(&self.arena).count() { + let num_of_children = parent.children(&self.arena).count(); + if index >= num_of_children { + let mut num_of_nodes_to_insert = index - num_of_children; + while num_of_nodes_to_insert > 0 { + self.append_nodes(&parent, vec![placeholder_node()]); + num_of_nodes_to_insert -= 1; + } + self.append_nodes(&parent, nodes); return Ok(()); } let node_to_insert = self - .child_from_node_at_index(parent, index) - .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; + .node_id_from_parent_at_index(parent, index) + .ok_or_else(|| OTError::internal().context(format!("Can't find the node at {}", index)))?; self.insert_nodes_before(&node_to_insert, nodes); Ok(()) @@ -252,45 +412,49 @@ impl NodeTree { } } - fn update_attributes(&mut self, path: &Path, attributes: AttributeHashMap) -> Result<(), OTError> { - self.mut_node_at_path(path, |node| { - let new_attributes = AttributeHashMap::compose(&node.attributes, &attributes)?; - node.attributes = new_attributes; - Ok(()) - }) - } - - fn delete_node(&mut self, path: &Path, nodes: Vec) -> Result<(), OTError> { - let mut update_node = self - .node_id_at_path(path) - .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; - - for _ in 0..nodes.len() { - let next = update_node.following_siblings(&self.arena).next(); - update_node.remove_subtree(&mut self.arena); - if let Some(next_id) = next { - update_node = next_id; - } else { - break; + /// Removes a node and its descendants from the tree + fn delete_node(&mut self, path: &Path) -> Result<(), OTError> { + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); + } + match self.node_id_at_path(path) { + None => tracing::warn!("Can't find any node at path: {:?}", path), + Some(node) => { + node.remove_subtree(&mut self.arena); } } + Ok(()) } - fn update_body(&mut self, path: &Path, changeset: NodeBodyChangeset) -> Result<(), OTError> { - self.mut_node_at_path(path, |node| { - node.apply_body_changeset(changeset); - Ok(()) - }) + /// Update the node at path with the `changeset` + /// + /// Do nothing if there is no node at the path. + /// + /// # Arguments + /// + /// * `path`: references to the node that will be applied with the changeset + /// * `changeset`: the change that will be applied to the node + /// + /// returns: Result<(), OTError> + fn update(&mut self, path: &Path, changeset: Changeset) -> Result<(), OTError> { + match self.mut_node_at_path(path, |node| node.apply_changeset(changeset)) { + Ok(_) => {} + Err(err) => tracing::error!("{}", err), + } + Ok(()) } fn mut_node_at_path(&mut self, path: &Path, f: F) -> Result<(), OTError> where F: FnOnce(&mut Node) -> Result<(), OTError>, { - let node_id = self - .node_id_at_path(path) - .ok_or_else(|| ErrorBuilder::new(OTErrorCode::PathNotFound).build())?; + if !path.is_valid() { + return Err(OTErrorCode::InvalidPath.into()); + } + let node_id = self.node_id_at_path(path).ok_or_else(|| { + OTError::path_not_found().context(format!("Can't find the mutated node at path: {:?}", path)) + })?; match self.arena.get_mut(node_id) { None => tracing::warn!("The path: {:?} does not contain any nodes", path), Some(node) => { @@ -301,3 +465,7 @@ impl NodeTree { Ok(()) } } + +pub fn placeholder_node() -> NodeData { + NodeData::new(PLACEHOLDER_NODE_TYPE) +} diff --git a/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs b/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs new file mode 100644 index 0000000000..a946a012c0 --- /dev/null +++ b/shared-lib/lib-ot/src/core/node_tree/tree_serde.rs @@ -0,0 +1,63 @@ +use crate::core::{NodeData, NodeTree, NodeTreeContext}; +use serde::de::{MapAccess, Visitor}; +use serde::ser::SerializeSeq; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; + +use std::marker::PhantomData; + +impl Serialize for NodeTree { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let root_node_id = self.root_node_id(); + let mut children = self.get_children_ids(root_node_id); + if children.is_empty() { + return serializer.serialize_str(""); + } + if children.len() == 1 { + let node_id = children.pop().unwrap(); + match self.get_node_data(node_id) { + None => serializer.serialize_str(""), + Some(node_data) => node_data.serialize(serializer), + } + } else { + let mut seq = serializer.serialize_seq(Some(children.len()))?; + for child in children { + if let Some(child_node_data) = self.get_node_data(child) { + let _ = seq.serialize_element(&child_node_data)?; + } + } + seq.end() + } + } +} + +impl<'de> Deserialize<'de> for NodeTree { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct NodeTreeVisitor(PhantomData); + + impl<'de> Visitor<'de> for NodeTreeVisitor { + type Value = NodeData; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("Expected node data tree") + } + + fn visit_map(self, map: V) -> Result + where + V: MapAccess<'de>, + { + // Forward the deserialization to NodeData + Deserialize::deserialize(de::value::MapAccessDeserializer::new(map)) + } + } + + let node_data: NodeData = deserializer.deserialize_any(NodeTreeVisitor(PhantomData))?; + Ok(NodeTree::from_node_data(node_data, NodeTreeContext::default()).unwrap()) + } +} diff --git a/shared-lib/lib-ot/src/errors.rs b/shared-lib/lib-ot/src/errors.rs index 9878c74947..86b01bec44 100644 --- a/shared-lib/lib-ot/src/errors.rs +++ b/shared-lib/lib-ot/src/errors.rs @@ -38,11 +38,14 @@ impl OTError { static_ot_error!(revision_id_conflict, OTErrorCode::RevisionIDConflict); static_ot_error!(internal, OTErrorCode::Internal); static_ot_error!(serde, OTErrorCode::SerdeError); + static_ot_error!(path_not_found, OTErrorCode::PathNotFound); + static_ot_error!(compose, OTErrorCode::ComposeOperationFail); + static_ot_error!(record_not_found, OTErrorCode::RecordNotFound); } impl fmt::Display for OTError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "incompatible lengths") + write!(f, "{:?}: {}", self.code, self.msg) } } @@ -74,7 +77,8 @@ pub enum OTErrorCode { Internal, PathNotFound, PathIsEmpty, - UnexpectedEmpty, + InvalidPath, + RecordNotFound, } pub struct ErrorBuilder { diff --git a/shared-lib/lib-ot/src/text_delta/attributes.rs b/shared-lib/lib-ot/src/text_delta/attributes.rs index b695d8a805..574130b214 100644 --- a/shared-lib/lib-ot/src/text_delta/attributes.rs +++ b/shared-lib/lib-ot/src/text_delta/attributes.rs @@ -1,6 +1,6 @@ #![allow(non_snake_case)] use crate::core::{AttributeEntry, AttributeHashMap, AttributeKey}; -use crate::text_delta::TextOperation; +use crate::text_delta::DeltaTextOperation; use crate::{inline_attribute_entry, inline_list_attribute_entry}; use lazy_static::lazy_static; use std::str::FromStr; @@ -12,7 +12,7 @@ pub fn empty_attributes() -> AttributeHashMap { AttributeHashMap::default() } -pub fn attributes_except_header(op: &TextOperation) -> AttributeHashMap { +pub fn attributes_except_header(op: &DeltaTextOperation) -> AttributeHashMap { let mut attributes = op.get_attributes(); attributes.remove_key(BuildInTextAttributeKey::Header); attributes diff --git a/shared-lib/lib-ot/src/text_delta/delta.rs b/shared-lib/lib-ot/src/text_delta/delta.rs index 526a27713d..dcb98f80cc 100644 --- a/shared-lib/lib-ot/src/text_delta/delta.rs +++ b/shared-lib/lib-ot/src/text_delta/delta.rs @@ -1,8 +1,8 @@ -use crate::core::{AttributeHashMap, DeltaOperation, DeltaOperations, OperationBuilder}; +use crate::core::{AttributeHashMap, DeltaOperation, DeltaOperationBuilder, DeltaOperations}; -pub type TextOperations = DeltaOperations; -pub type TextOperationBuilder = OperationBuilder; -pub type TextOperation = DeltaOperation; +pub type DeltaTextOperations = DeltaOperations; +pub type DeltaTextOperationBuilder = DeltaOperationBuilder; +pub type DeltaTextOperation = DeltaOperation; // pub trait TextOperation2: Default + Debug + OperationTransform {} // diff --git a/shared-lib/lib-ot/tests/node/changeset_compose_test.rs b/shared-lib/lib-ot/tests/node/changeset_compose_test.rs new file mode 100644 index 0000000000..7e800952be --- /dev/null +++ b/shared-lib/lib-ot/tests/node/changeset_compose_test.rs @@ -0,0 +1,194 @@ +use crate::node::script::NodeScript::*; +use crate::node::script::NodeTest; +use lib_ot::core::{AttributeEntry, Changeset, NodeData, OperationTransform}; +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[test] +fn changeset_delta_compose_delta_test() { + // delta 1 + let delta_1 = DeltaTextOperationBuilder::new().insert("Hello world").build(); + let inverted_1 = delta_1.inverted(); + let mut changeset_1 = Changeset::Delta { + delta: delta_1.clone(), + inverted: inverted_1, + }; + + // delta 2 + let delta_2 = DeltaTextOperationBuilder::new() + .retain(delta_1.utf16_target_len) + .insert("!") + .build(); + let inverted_2 = delta_2.inverted(); + let changeset_2 = Changeset::Delta { + delta: delta_2, + inverted: inverted_2, + }; + + // compose + changeset_1.compose(&changeset_2).unwrap(); + + if let Changeset::Delta { delta, inverted } = changeset_1 { + assert_eq!(delta.content().unwrap(), "Hello world!"); + let new_delta = delta.compose(&inverted).unwrap(); + assert_eq!(new_delta.content().unwrap(), ""); + } +} + +#[test] +fn operation_compose_delta_changeset_then_invert_test() { + let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); + let inverted = delta.inverted(); + let changeset = Changeset::Delta { + delta: delta.clone(), + inverted: inverted.clone(), + }; + + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta.compose(&inverted).unwrap(), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_compose_multiple_delta_changeset_then_invert_test() { + // delta 1 + let delta_1 = DeltaTextOperationBuilder::new().insert("Hello world").build(); + let inverted_1 = delta_1.inverted(); + let changeset_1 = Changeset::Delta { + delta: delta_1.clone(), + inverted: inverted_1, + }; + + // delta 2 + let delta_2 = DeltaTextOperationBuilder::new() + .retain(delta_1.utf16_target_len) + .insert("!") + .build(); + let inverted_2 = delta_2.inverted(); + let changeset_2 = Changeset::Delta { + delta: delta_2.clone(), + inverted: inverted_2, + }; + + // delta 3 + let delta_3 = DeltaTextOperationBuilder::new() + .retain(delta_2.utf16_target_len) + .insert("AppFlowy") + .build(); + let inverted_3 = delta_3.inverted(); + let changeset_3 = Changeset::Delta { + delta: delta_3.clone(), + inverted: inverted_3, + }; + + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_1.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_2.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_3.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected: delta_1.compose(&delta_2).unwrap().compose(&delta_3).unwrap(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset_3.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#"Hello world!"#, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_2.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#"Hello world"#, + }, + UpdateBody { + path: 0.into(), + changeset: changeset_1.inverted(), + }, + AssertNodeDeltaContent { + path: 0.into(), + expected: r#""#, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +#[should_panic] +fn changeset_delta_compose_attributes_test() { + // delta 1 + let delta = DeltaTextOperationBuilder::new().insert("Hello world").build(); + let inverted = delta.inverted(); + let mut delta_changeset = Changeset::Delta { delta, inverted }; + + // attributes + let attribute_changeset = Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }; + + // compose + delta_changeset.compose(&attribute_changeset).unwrap(); +} + +#[test] +fn changeset_attributes_compose_attributes_test() { + // attributes + let mut changeset_1 = Changeset::Attributes { + new: AttributeEntry::new("bold", true).into(), + old: Default::default(), + }; + + let changeset_2 = Changeset::Attributes { + new: AttributeEntry::new("Italic", true).into(), + old: Default::default(), + }; + // compose + changeset_1.compose(&changeset_2).unwrap(); + + if let Changeset::Attributes { new, old: _ } = changeset_1 { + assert_eq!(new, AttributeEntry::new("Italic", true).into()); + } +} diff --git a/shared-lib/lib-ot/tests/node/editor_test.rs b/shared-lib/lib-ot/tests/node/editor_test.rs deleted file mode 100644 index 1695b015c0..0000000000 --- a/shared-lib/lib-ot/tests/node/editor_test.rs +++ /dev/null @@ -1,164 +0,0 @@ -use super::script::{NodeScript::*, *}; -use lib_ot::core::AttributeBuilder; -use lib_ot::{ - core::{NodeData, Path}, - text_delta::TextOperationBuilder, -}; - -#[test] -fn editor_deserialize_node_test() { - let mut test = NodeTest::new(); - let node: NodeData = serde_json::from_str(EXAMPLE_JSON).unwrap(); - let path: Path = 0.into(); - - let expected_delta = TextOperationBuilder::new() - .insert("👋 ") - .insert_with_attributes( - "Welcome to ", - AttributeBuilder::new().insert("href", "appflowy.io").build(), - ) - .insert_with_attributes( - "AppFlowy Editor", - AttributeBuilder::new().insert("italic", true).build(), - ) - .build(); - - test.run_scripts(vec![ - InsertNode { - path, - node_data: node.clone(), - rev_id: 1, - }, - AssertNumberOfNodesAtPath { path: None, len: 1 }, - AssertNumberOfNodesAtPath { - path: Some(0.into()), - len: 14, - }, - AssertNumberOfNodesAtPath { - path: Some(0.into()), - len: 14, - }, - AssertNodeDelta { - path: vec![0, 1].into(), - expected: expected_delta, - }, - AssertNodeData { - path: vec![0, 0].into(), - expected: Some(node.children[0].clone()), - }, - AssertNodeData { - path: vec![0, 3].into(), - expected: Some(node.children[3].clone()), - }, - ]); -} - -#[allow(dead_code)] -const EXAMPLE_JSON: &str = r#" -{ - "type": "editor", - "children": [ - { - "type": "image", - "attributes": { - "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg", - "align": "center" - } - }, - { - "type": "text", - "attributes": { - "subtype": "heading", - "heading": "h1" - }, - "body": { - "delta": [ - { - "insert": "👋 " - }, - { - "insert": "Welcome to ", - "attributes": { - "href": "appflowy.io" - } - }, - { - "insert": "AppFlowy Editor", - "attributes": { - "italic": true - } - } - ] - } - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "body": { - "delta": [ - { "insert": "AppFlowy Editor is a " }, - { "insert": "highly customizable", "attributes": { "bold": true } }, - { "insert": " " }, - { "insert": "rich-text editor", "attributes": { "italic": true } }, - { "insert": " for " }, - { "insert": "Flutter", "attributes": { "underline": true } } - ] - } - }, - { - "type": "text", - "attributes": { "checkbox": true, "subtype": "checkbox" }, - "body": { - "delta": [{ "insert": "Customizable" }] - } - }, - { - "type": "text", - "attributes": { "checkbox": true, "subtype": "checkbox" }, - "delta": [{ "insert": "Test-covered" }] - }, - { - "type": "text", - "attributes": { "checkbox": false, "subtype": "checkbox" }, - "delta": [{ "insert": "more to come!" }] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "attributes": { "subtype": "quote" }, - "delta": [{ "insert": "Here is an exmaple you can give it a try" }] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "delta": [ - { "insert": "You can also use " }, - { - "insert": "AppFlowy Editor", - "attributes": { - "italic": true, - "bold": true, - "backgroundColor": "0x6000BCF0" - } - }, - { "insert": " as a component to build your own app." } - ] - }, - { "type": "text", "delta": [] }, - { - "type": "text", - "attributes": { "subtype": "bulleted-list" }, - "delta": [{ "insert": "Use / to insert blocks" }] - }, - { - "type": "text", - "attributes": { "subtype": "bulleted-list" }, - "delta": [ - { - "insert": "Select text to trigger to the toolbar to format your notes." - } - ] - } - ] -} -"#; diff --git a/shared-lib/lib-ot/tests/node/mod.rs b/shared-lib/lib-ot/tests/node/mod.rs index dddad56eb5..904f44f4d8 100644 --- a/shared-lib/lib-ot/tests/node/mod.rs +++ b/shared-lib/lib-ot/tests/node/mod.rs @@ -1,4 +1,10 @@ -mod editor_test; -mod operation_test; +mod changeset_compose_test; +mod operation_attribute_test; +mod operation_compose_test; +mod operation_delete_test; +mod operation_delta_test; +mod operation_insert_test; mod script; +mod serde_test; +mod transaction_compose_test; mod tree_test; diff --git a/shared-lib/lib-ot/tests/node/operation_attribute_test.rs b/shared-lib/lib-ot/tests/node/operation_attribute_test.rs new file mode 100644 index 0000000000..cb7308d9d6 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/operation_attribute_test.rs @@ -0,0 +1,64 @@ +use crate::node::script::NodeScript::*; +use crate::node::script::NodeTest; +use lib_ot::core::{AttributeEntry, AttributeValue, Changeset, NodeData}; + +#[test] +fn operation_update_attribute_with_float_value_test() { + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", 12.2).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":12.2}"#, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_update_attribute_with_negative_value_test() { + let mut test = NodeTest::new(); + let text_node = NodeData::new("text"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", -12.2).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":-12.2}"#, + }, + UpdateBody { + path: 0.into(), + changeset: Changeset::Attributes { + new: AttributeEntry::new("value", AttributeValue::from_int(-12)).into(), + old: Default::default(), + }, + }, + AssertNodeAttributes { + path: 0.into(), + expected: r#"{"value":-12}"#, + }, + ]; + test.run_scripts(scripts); +} diff --git a/shared-lib/lib-ot/tests/node/operation_compose_test.rs b/shared-lib/lib-ot/tests/node/operation_compose_test.rs new file mode 100644 index 0000000000..698add3387 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/operation_compose_test.rs @@ -0,0 +1,135 @@ +use lib_ot::core::{Changeset, NodeOperation}; + +#[test] +fn operation_insert_compose_delta_update_test() { + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Delta { + delta: Default::default(), + inverted: Default::default(), + }, + }; + + assert!(insert_operation.can_compose(&update_operation)) +} + +#[test] +fn operation_insert_compose_attribute_update_test() { + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + + assert!(!insert_operation.can_compose(&update_operation)) +} +#[test] +fn operation_insert_compose_update_with_diff_path_test() { + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + + let update_operation = NodeOperation::Update { + path: 1.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + + assert!(!insert_operation.can_compose(&update_operation)) +} + +#[test] +fn operation_insert_compose_insert_operation_test() { + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + + assert!(!insert_operation.can_compose(&NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }),) +} + +#[test] +fn operation_update_compose_insert_operation_test() { + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + + assert!(!update_operation.can_compose(&NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + })) +} +#[test] +fn operation_update_compose_update_test() { + let update_operation_1 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + let update_operation_2 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + + assert!(update_operation_1.can_compose(&update_operation_2)); +} +#[test] +fn operation_update_compose_update_with_diff_path_test() { + let update_operation_1 = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + let update_operation_2 = NodeOperation::Update { + path: 1.into(), + changeset: Changeset::Attributes { + new: Default::default(), + old: Default::default(), + }, + }; + + assert!(!update_operation_1.can_compose(&update_operation_2)); +} + +#[test] +fn operation_insert_compose_insert_test() { + let insert_operation_1 = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + let insert_operation_2 = NodeOperation::Insert { + path: 0.into(), + nodes: vec![], + }; + + assert!(!insert_operation_1.can_compose(&insert_operation_2)); +} diff --git a/shared-lib/lib-ot/tests/node/operation_delete_test.rs b/shared-lib/lib-ot/tests/node/operation_delete_test.rs new file mode 100644 index 0000000000..145699c9b2 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/operation_delete_test.rs @@ -0,0 +1,178 @@ +use crate::node::script::NodeScript::*; +use crate::node::script::NodeTest; + +use lib_ot::core::{Changeset, NodeData, NodeDataBuilder}; + +#[test] +fn operation_delete_nested_node_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + + let video_a = NodeData::new("video_a"); + let video_b = NodeData::new("video_b"); + + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let video_1 = NodeDataBuilder::new("video_1") + .add_node_data(video_a.clone()) + .add_node_data(video_b) + .build(); + + let text_node_1 = NodeDataBuilder::new("text_1") + .add_node_data(image_1) + .add_node_data(video_1.clone()) + .build(); + + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2, + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:video_1 + // 0:video_a + // 1:video_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 3, + }, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b), + }, + AssertNode { + path: vec![0, 1].into(), + expected: Some(video_1), + }, + DeleteNode { + path: vec![0, 1, 1].into(), + rev_id: 4, + }, + AssertNode { + path: vec![0, 1, 0].into(), + expected: Some(video_a), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_delete_node_with_revision_conflict_test() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let text_3 = NodeDataBuilder::new("text_3").build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_2, + rev_id: 2, + }, + // The node's in the tree will be: + // 0: text_1 + // 2: text_2 + // + // The insert action is happened concurrently with the delete action, because they + // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1, + // but it was moved to index 2. + InsertNode { + path: 1.into(), + node_data: text_3.clone(), + rev_id: 3, + }, + // 0: text_1 + // 1: text_3 + // 2: text_2 + // + // The path of the delete action will be transformed to a new path that point to the text_2. + // 1 -> 2 + DeleteNode { + path: 1.into(), + rev_id: 3, + }, + // After perform the delete action, the tree will be: + // 0: text_1 + // 1: text_3 + AssertNumberOfChildrenAtPath { + path: None, + expected: 2, + }, + AssertNode { + path: 0.into(), + expected: Some(text_1), + }, + AssertNode { + path: 1.into(), + expected: Some(text_3), + }, + AssertNode { + path: 2.into(), + expected: None, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_update_node_after_delete_test() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_2, + rev_id: 2, + }, + DeleteNode { + path: 0.into(), + rev_id: 3, + }, + // The node at path 1 is not exist. The following UpdateBody script will do nothing + AssertNode { + path: 1.into(), + expected: None, + }, + UpdateBody { + path: 1.into(), + changeset: Changeset::Delta { + delta: Default::default(), + inverted: Default::default(), + }, + }, + ]; + test.run_scripts(scripts); +} diff --git a/shared-lib/lib-ot/tests/node/operation_delta_test.rs b/shared-lib/lib-ot/tests/node/operation_delta_test.rs new file mode 100644 index 0000000000..bdc2812158 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/operation_delta_test.rs @@ -0,0 +1,41 @@ +use crate::node::script::NodeScript::{AssertNodeDelta, InsertNode, UpdateBody}; +use crate::node::script::{edit_node_delta, NodeTest}; +use lib_ot::core::NodeDataBuilder; +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[test] +fn operation_update_delta_test() { + let mut test = NodeTest::new(); + let initial_delta = DeltaTextOperationBuilder::new().build(); + let new_delta = DeltaTextOperationBuilder::new() + .retain(initial_delta.utf16_base_len) + .insert("Hello, world") + .build(); + let (changeset, expected) = edit_node_delta(&initial_delta, new_delta); + let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + AssertNodeDelta { + path: 0.into(), + expected, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: initial_delta, + }, + ]; + test.run_scripts(scripts); +} diff --git a/shared-lib/lib-ot/tests/node/operation_insert_test.rs b/shared-lib/lib-ot/tests/node/operation_insert_test.rs new file mode 100644 index 0000000000..50cc57d130 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/operation_insert_test.rs @@ -0,0 +1,460 @@ +use crate::node::script::NodeScript::*; +use crate::node::script::NodeTest; + +use lib_ot::core::{placeholder_node, NodeData, NodeDataBuilder, NodeOperation, Path}; + +#[test] +fn operation_insert_op_transform_test() { + let node_1 = NodeDataBuilder::new("text_1").build(); + let node_2 = NodeDataBuilder::new("text_2").build(); + let op_1 = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node_1], + }; + + let mut insert_2 = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node_2], + }; + + // let mut node_tree = NodeTree::new("root"); + // node_tree.apply_op(insert_1.clone()).unwrap(); + + op_1.transform(&mut insert_2); + let json = serde_json::to_string(&insert_2).unwrap(); + assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); +} + +#[test] +fn operation_insert_one_level_path_test() { + let node_data_1 = NodeDataBuilder::new("text_1").build(); + let node_data_2 = NodeDataBuilder::new("text_2").build(); + let node_data_3 = NodeDataBuilder::new("text_3").build(); + let node_3 = node_data_3.clone(); + // 0: text_1 + // 1: text_2 + // + // Insert a new operation with rev_id 2 to index 1,but the index was already taken, so + // it needs to be transformed. + // + // 0: text_1 + // 1: text_2 + // 2: text_3 + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2.clone(), + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3.clone(), + rev_id: 2, + }, + AssertNode { + path: 2.into(), + expected: Some(node_3.clone()), + }, + ]; + NodeTest::new().run_scripts(scripts); + + // If the rev_id of the node_data_3 is 3. then the tree will be: + // 0: text_1 + // 1: text_3 + // 2: text_2 + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2, + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3, + rev_id: 3, + }, + AssertNode { + path: 1.into(), + expected: Some(node_3), + }, + ]; + NodeTest::new().run_scripts(scripts); +} + +#[test] +fn operation_insert_with_multiple_level_path_test() { + let mut test = NodeTest::new(); + let node_data_1 = NodeDataBuilder::new("text_1") + .add_node_data(NodeDataBuilder::new("text_1_1").build()) + .add_node_data(NodeDataBuilder::new("text_1_2").build()) + .build(); + + let node_data_2 = NodeDataBuilder::new("text_2") + .add_node_data(NodeDataBuilder::new("text_2_1").build()) + .add_node_data(NodeDataBuilder::new("text_2_2").build()) + .build(); + + let node_data_3 = NodeDataBuilder::new("text_3").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_data_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_data_2, + rev_id: 2, + }, + InsertNode { + path: 1.into(), + node_data: node_data_3.clone(), + rev_id: 2, + }, + AssertNode { + path: 2.into(), + expected: Some(node_data_3), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_out_of_bound_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + let image = NodeDataBuilder::new("image_1") + .add_node_data(image_a) + .add_node_data(image_b) + .build(); + let text_node = NodeDataBuilder::new("text_1").add_node_data(image).build(); + let image_c = NodeData::new("image_c"); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node, + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + InsertNode { + path: vec![0, 0, 3].into(), + node_data: image_c.clone(), + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 2:placeholder node + // 3:image_c + AssertNode { + path: vec![0, 0, 2].into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: vec![0, 0, 3].into(), + expected: Some(image_c), + }, + AssertNode { + path: vec![0, 0, 10].into(), + expected: None, + }, + ]; + test.run_scripts(scripts); +} +#[test] +fn operation_insert_node_when_parent_is_not_exist_test1() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: 2.into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_parent_is_not_exist_test2() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: 3.into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 3.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_its_parent_is_not_exist_test3() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + + let mut placeholder_node = placeholder_node(); + placeholder_node.children.push(text_2.clone()); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + // The node at path 1 is not existing when inserting the text_2 to path 2. + InsertNode { + path: vec![1, 0].into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node), + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_to_the_end_when_parent_is_not_exist_test() { + let mut test = NodeTest::new(); + let node_0 = NodeData::new("0"); + let node_1 = NodeData::new("1"); + let node_1_1 = NodeData::new("1_1"); + let text_node = NodeData::new("text"); + let mut ghost = placeholder_node(); + ghost.children.push(text_node.clone()); + // 0:0 + // 1:1 + // 0:1_1 + // 1:ghost + // 0:text + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_0, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_1, + rev_id: 2, + }, + InsertNode { + path: vec![1, 0].into(), + node_data: node_1_1.clone(), + rev_id: 3, + }, + InsertNode { + path: vec![1, 1, 0].into(), + node_data: text_node.clone(), + rev_id: 4, + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(node_1_1), + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(ghost), + }, + AssertNode { + path: vec![1, 1, 0].into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test() { + let mut test = NodeTest::new(); + let text_1 = NodeDataBuilder::new("text_1").build(); + let text_2 = NodeDataBuilder::new("text_2").build(); + + let path = vec![1, 0, 0, 0, 0, 0]; + let mut auto_fill_node = placeholder_node(); + let mut iter_node: &mut NodeData = &mut auto_fill_node; + let insert_path = path.split_at(1).1; + for (index, _) in insert_path.iter().enumerate() { + if index == insert_path.len() - 1 { + iter_node.children.push(text_2.clone()); + } else { + iter_node.children.push(placeholder_node()); + iter_node = iter_node.children.last_mut().unwrap(); + } + } + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_1, + rev_id: 1, + }, + InsertNode { + path: path.clone().into(), + node_data: text_2.clone(), + rev_id: 2, + }, + AssertNode { + path: vec![1].into(), + expected: Some(auto_fill_node), + }, + AssertNode { + path: path.into(), + expected: Some(text_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test2() { + let mut test = NodeTest::new(); + // 0:ghost + // 0:ghost + // 1:ghost + // 0:text + let mut text_node_parent = placeholder_node(); + let text_node = NodeDataBuilder::new("text").build(); + text_node_parent.children.push(text_node.clone()); + + let mut ghost = placeholder_node(); + ghost.children.push(placeholder_node()); + ghost.children.push(text_node_parent.clone()); + + let path = vec![1, 1, 0]; + let scripts = vec![ + InsertNode { + path: path.into(), + node_data: text_node.clone(), + rev_id: 1, + }, + // 0:ghost + // 1:ghost + // 0:ghost + // 1:ghost + // 0:text + AssertNode { + path: 0.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 1.into(), + expected: Some(ghost), + }, + AssertNumberOfChildrenAtPath { + path: Some(1.into()), + expected: 2, + }, + AssertNode { + path: vec![1, 1].into(), + expected: Some(text_node_parent), + }, + AssertNode { + path: vec![1, 1, 0].into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn operation_insert_node_when_multiple_parent_is_not_exist_test3() { + let mut test = NodeTest::new(); + let text_node = NodeDataBuilder::new("text").build(); + let path = vec![3, 3, 0]; + let scripts = vec![ + InsertNode { + path: path.clone().into(), + node_data: text_node.clone(), + rev_id: 1, + }, + // 0:ghost + // 1:ghost + // 2:ghost + // 3:ghost + // 0:ghost + // 1:ghost + // 2:ghost + // 3:ghost + // 0:text + AssertNode { + path: 0.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 1.into(), + expected: Some(placeholder_node()), + }, + AssertNode { + path: 2.into(), + expected: Some(placeholder_node()), + }, + AssertNumberOfChildrenAtPath { + path: Some(3.into()), + expected: 4, + }, + AssertNode { + path: path.into(), + expected: Some(text_node), + }, + ]; + test.run_scripts(scripts); +} diff --git a/shared-lib/lib-ot/tests/node/operation_test.rs b/shared-lib/lib-ot/tests/node/operation_test.rs deleted file mode 100644 index 114b2abb01..0000000000 --- a/shared-lib/lib-ot/tests/node/operation_test.rs +++ /dev/null @@ -1,235 +0,0 @@ -use crate::node::script::NodeScript::*; -use crate::node::script::NodeTest; -use lib_ot::core::{AttributeBuilder, Node}; -use lib_ot::{ - core::{NodeBodyChangeset, NodeData, NodeDataBuilder, NodeOperation, Path}, - text_delta::TextOperationBuilder, -}; - -#[test] -fn operation_insert_node_serde_test() { - let insert = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![NodeData::new("text".to_owned())], - }; - let result = serde_json::to_string(&insert).unwrap(); - assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); -} - -#[test] -fn operation_insert_node_with_children_serde_test() { - let node = NodeDataBuilder::new("text") - .add_node(NodeData::new("sub_text".to_owned())) - .build(); - - let insert = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node], - }; - assert_eq!( - serde_json::to_string(&insert).unwrap(), - r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"# - ); -} -#[test] -fn operation_update_node_attributes_serde_test() { - let operation = NodeOperation::UpdateAttributes { - path: Path(vec![0, 1]), - new: AttributeBuilder::new().insert("bold", true).build(), - old: AttributeBuilder::new().insert("bold", false).build(), - }; - - let result = serde_json::to_string(&operation).unwrap(); - assert_eq!( - result, - r#"{"op":"update-attribute","path":[0,1],"new":{"bold":true},"old":{"bold":null}}"# - ); -} - -#[test] -fn operation_update_node_body_serialize_test() { - let delta = TextOperationBuilder::new().insert("AppFlowy...").build(); - let inverted = delta.invert_str(""); - let changeset = NodeBodyChangeset::Delta { delta, inverted }; - let insert = NodeOperation::UpdateBody { - path: Path(vec![0, 1]), - changeset, - }; - let result = serde_json::to_string(&insert).unwrap(); - assert_eq!( - result, - r#"{"op":"update-body","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"# - ); - // -} - -#[test] -fn operation_update_node_body_deserialize_test() { - let json_1 = r#"{"op":"update-body","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#; - let operation: NodeOperation = serde_json::from_str(json_1).unwrap(); - let json_2 = serde_json::to_string(&operation).unwrap(); - assert_eq!(json_1, json_2); -} - -#[test] -fn operation_insert_op_transform_test() { - let node_1 = NodeDataBuilder::new("text_1").build(); - let node_2 = NodeDataBuilder::new("text_2").build(); - let op_1 = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node_1], - }; - - let mut insert_2 = NodeOperation::Insert { - path: Path(vec![0, 1]), - nodes: vec![node_2], - }; - - // let mut node_tree = NodeTree::new("root"); - // node_tree.apply_op(insert_1.clone()).unwrap(); - - op_1.transform(&mut insert_2); - let json = serde_json::to_string(&insert_2).unwrap(); - assert_eq!(json, r#"{"op":"insert","path":[0,2],"nodes":[{"type":"text_2"}]}"#); -} - -#[test] -fn operation_insert_transform_one_level_path_test() { - let mut test = NodeTest::new(); - let node_data_1 = NodeDataBuilder::new("text_1").build(); - let node_data_2 = NodeDataBuilder::new("text_2").build(); - let node_data_3 = NodeDataBuilder::new("text_3").build(); - let node_3: Node = node_data_3.clone().into(); - // 0: text_1 - // 1: text_2 - // - // Insert a new operation with rev_id 1,but the rev_id:1 is already exist, so - // it needs to be transformed. - // 1:text_3 => 2:text_3 - // - // 0: text_1 - // 1: text_2 - // 2: text_3 - // - // If the rev_id of the insert operation is 3. then the tree will be: - // 0: text_1 - // 1: text_3 - // 2: text_2 - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - InsertNode { - path: 1.into(), - node_data: node_data_3, - rev_id: 1, - }, - AssertNode { - path: 2.into(), - expected: Some(node_3), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_insert_transform_multiple_level_path_test() { - let mut test = NodeTest::new(); - let node_data_1 = NodeDataBuilder::new("text_1") - .add_node(NodeDataBuilder::new("text_1_1").build()) - .add_node(NodeDataBuilder::new("text_1_2").build()) - .build(); - - let node_data_2 = NodeDataBuilder::new("text_2") - .add_node(NodeDataBuilder::new("text_2_1").build()) - .add_node(NodeDataBuilder::new("text_2_2").build()) - .build(); - - let node_data_3 = NodeDataBuilder::new("text_3").build(); - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - InsertNode { - path: 1.into(), - node_data: node_data_3.clone(), - rev_id: 1, - }, - AssertNode { - path: 2.into(), - expected: Some(node_data_3.into()), - }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn operation_delete_transform_path_test() { - let mut test = NodeTest::new(); - let node_data_1 = NodeDataBuilder::new("text_1").build(); - let node_data_2 = NodeDataBuilder::new("text_2").build(); - let node_data_3 = NodeDataBuilder::new("text_3").build(); - let node_3: Node = node_data_3.clone().into(); - - let scripts = vec![ - InsertNode { - path: 0.into(), - node_data: node_data_1, - rev_id: 1, - }, - InsertNode { - path: 1.into(), - node_data: node_data_2, - rev_id: 2, - }, - // The node's in the tree will be: - // 0: text_1 - // 2: text_2 - // - // The insert action is happened concurrently with the delete action, because they - // share the same rev_id. aka, 3. The delete action is want to delete the node at index 1, - // but it was moved to index 2. - InsertNode { - path: 1.into(), - node_data: node_data_3, - rev_id: 3, - }, - // 0: text_1 - // 1: text_3 - // 2: text_2 - // - // The path of the delete action will be transformed to a new path that point to the text_2. - // 1 -> 2 - DeleteNode { - path: 1.into(), - rev_id: 3, - }, - // After perform the delete action, the tree will be: - // 0: text_1 - // 1: text_3 - AssertNumberOfNodesAtPath { path: None, len: 2 }, - AssertNode { - path: 1.into(), - expected: Some(node_3), - }, - AssertNode { - path: 2.into(), - expected: None, - }, - ]; - test.run_scripts(scripts); -} diff --git a/shared-lib/lib-ot/tests/node/script.rs b/shared-lib/lib-ot/tests/node/script.rs index 7c27763bc7..7fff043653 100644 --- a/shared-lib/lib-ot/tests/node/script.rs +++ b/shared-lib/lib-ot/tests/node/script.rs @@ -1,8 +1,10 @@ -use lib_ot::core::{Node, Transaction}; +#![allow(clippy::all)] +use lib_ot::core::{NodeTreeContext, OperationTransform, Transaction}; +use lib_ot::text_delta::DeltaTextOperationBuilder; use lib_ot::{ core::attributes::AttributeHashMap, - core::{NodeBody, NodeBodyChangeset, NodeData, NodeTree, Path, TransactionBuilder}, - text_delta::TextOperations, + core::{Body, Changeset, NodeData, NodeTree, Path, TransactionBuilder}, + text_delta::DeltaTextOperations, }; use std::collections::HashMap; @@ -12,33 +14,54 @@ pub enum NodeScript { node_data: NodeData, rev_id: usize, }, + InsertNodes { + path: Path, + node_data_list: Vec, + rev_id: usize, + }, UpdateAttributes { path: Path, attributes: AttributeHashMap, }, UpdateBody { path: Path, - changeset: NodeBodyChangeset, + changeset: Changeset, }, DeleteNode { path: Path, rev_id: usize, }, - AssertNumberOfNodesAtPath { + AssertNumberOfChildrenAtPath { path: Option, - len: usize, + expected: usize, }, - AssertNodeData { + AssertNodesAtRoot { + expected: Vec, + }, + #[allow(dead_code)] + AssertNodesAtPath { path: Path, - expected: Option, + expected: Vec, }, AssertNode { path: Path, - expected: Option, + expected: Option, + }, + AssertNodeAttributes { + path: Path, + expected: &'static str, }, AssertNodeDelta { path: Path, - expected: TextOperations, + expected: DeltaTextOperations, + }, + AssertNodeDeltaContent { + path: Path, + expected: &'static str, + }, + #[allow(dead_code)] + AssertTreeJSON { + expected: String, }, } @@ -53,7 +76,7 @@ impl NodeTest { Self { rev_id: 0, rev_operations: HashMap::new(), - node_tree: NodeTree::new("root"), + node_tree: NodeTree::new(NodeTreeContext::default()), } } @@ -70,74 +93,94 @@ impl NodeTest { node_data: node, rev_id, } => { - let mut transaction = TransactionBuilder::new(&self.node_tree) - .insert_node_at_path(path, node) - .finalize(); + let mut transaction = TransactionBuilder::new().insert_node_at_path(path, node).build(); + self.transform_transaction_if_need(&mut transaction, rev_id); + self.apply_transaction(transaction); + } + NodeScript::InsertNodes { + path, + node_data_list, + rev_id, + } => { + let mut transaction = TransactionBuilder::new() + .insert_nodes_at_path(path, node_data_list) + .build(); self.transform_transaction_if_need(&mut transaction, rev_id); self.apply_transaction(transaction); } NodeScript::UpdateAttributes { path, attributes } => { - let transaction = TransactionBuilder::new(&self.node_tree) - .update_attributes_at_path(&path, attributes) - .finalize(); + let node = self.node_tree.get_node_data_at_path(&path).unwrap(); + let transaction = TransactionBuilder::new() + .update_node_at_path( + &path, + Changeset::Attributes { + new: attributes, + old: node.attributes, + }, + ) + .build(); self.apply_transaction(transaction); } NodeScript::UpdateBody { path, changeset } => { // - let transaction = TransactionBuilder::new(&self.node_tree) - .update_body_at_path(&path, changeset) - .finalize(); + let transaction = TransactionBuilder::new().update_node_at_path(&path, changeset).build(); self.apply_transaction(transaction); } NodeScript::DeleteNode { path, rev_id } => { - let mut transaction = TransactionBuilder::new(&self.node_tree) - .delete_node_at_path(&path) - .finalize(); + let mut transaction = TransactionBuilder::new() + .delete_node_at_path(&self.node_tree, &path) + .build(); self.transform_transaction_if_need(&mut transaction, rev_id); self.apply_transaction(transaction); } + NodeScript::AssertNode { path, expected } => { - let node_id = self.node_tree.node_id_at_path(path); - if expected.is_none() && node_id.is_none() { - return; - } - - let node = self.node_tree.get_node(node_id.unwrap()).cloned(); - assert_eq!(node, expected); + let node = self.node_tree.get_node_data_at_path(&path); + assert_eq!(node, expected.map(|e| e.into())); } - NodeScript::AssertNodeData { path, expected } => { - let node_id = self.node_tree.node_id_at_path(path); - - match node_id { - None => assert!(node_id.is_none()), - Some(node_id) => { - let node = self.node_tree.get_node(node_id).cloned(); - assert_eq!(node, expected.map(|e| e.into())); - } - } + NodeScript::AssertNodeAttributes { path, expected } => { + let node = self.node_tree.get_node_data_at_path(&path).unwrap(); + assert_eq!(node.attributes.to_json().unwrap(), expected); } - NodeScript::AssertNumberOfNodesAtPath { - path, - len: expected_len, - } => match path { + NodeScript::AssertNumberOfChildrenAtPath { path, expected } => match path { None => { let len = self.node_tree.number_of_children(None); - assert_eq!(len, expected_len) + assert_eq!(len, expected) } Some(path) => { let node_id = self.node_tree.node_id_at_path(path).unwrap(); let len = self.node_tree.number_of_children(Some(node_id)); - assert_eq!(len, expected_len) + assert_eq!(len, expected) } }, + NodeScript::AssertNodesAtRoot { expected } => { + let nodes = self.node_tree.get_node_data_at_root().unwrap().children; + assert_eq!(nodes, expected) + } + NodeScript::AssertNodesAtPath { path, expected } => { + let nodes = self.node_tree.get_node_data_at_path(&path).unwrap().children; + assert_eq!(nodes, expected) + } NodeScript::AssertNodeDelta { path, expected } => { let node = self.node_tree.get_node_at_path(&path).unwrap(); - if let NodeBody::Delta(delta) = node.body.clone() { + if let Body::Delta(delta) = node.body.clone() { debug_assert_eq!(delta, expected); } else { panic!("Node body type not match, expect Delta"); } } + NodeScript::AssertNodeDeltaContent { path, expected } => { + let node = self.node_tree.get_node_at_path(&path).unwrap(); + if let Body::Delta(delta) = node.body.clone() { + debug_assert_eq!(delta.content().unwrap(), expected); + } else { + panic!("Node body type not match, expect Delta"); + } + } + NodeScript::AssertTreeJSON { expected } => { + let json = serde_json::to_string(&self.node_tree).unwrap(); + assert_eq!(json, expected) + } } } @@ -156,3 +199,35 @@ impl NodeTest { } } } + +pub fn edit_node_delta( + delta: &DeltaTextOperations, + new_delta: DeltaTextOperations, +) -> (Changeset, DeltaTextOperations) { + let inverted = new_delta.invert(&delta); + let expected = delta.compose(&new_delta).unwrap(); + let changeset = Changeset::Delta { + delta: new_delta.clone(), + inverted: inverted.clone(), + }; + (changeset, expected) +} + +pub fn make_node_delta_changeset( + initial_content: &str, + insert_str: &str, +) -> (DeltaTextOperations, Changeset, DeltaTextOperations) { + let initial_content = initial_content.to_owned(); + let initial_delta = DeltaTextOperationBuilder::new().insert(&initial_content).build(); + let delta = DeltaTextOperationBuilder::new() + .retain(initial_content.len()) + .insert(insert_str) + .build(); + let inverted = delta.invert(&initial_delta); + let expected = initial_delta.compose(&delta).unwrap(); + let changeset = Changeset::Delta { + delta: delta.clone(), + inverted: inverted.clone(), + }; + (initial_delta, changeset, expected) +} diff --git a/shared-lib/lib-ot/tests/node/serde_test.rs b/shared-lib/lib-ot/tests/node/serde_test.rs new file mode 100644 index 0000000000..b3a76d06d2 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/serde_test.rs @@ -0,0 +1,150 @@ +use lib_ot::core::{AttributeBuilder, Changeset, NodeData, NodeDataBuilder, NodeOperation, NodeTree, Path}; +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[test] +fn operation_insert_node_serde_test() { + let insert = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![NodeData::new("text".to_owned())], + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!(result, r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}"#); +} + +#[test] +fn operation_insert_node_with_children_serde_test() { + let node = NodeDataBuilder::new("text") + .add_node_data(NodeData::new("sub_text".to_owned())) + .build(); + + let insert = NodeOperation::Insert { + path: Path(vec![0, 1]), + nodes: vec![node], + }; + assert_eq!( + serde_json::to_string(&insert).unwrap(), + r#"{"op":"insert","path":[0,1],"nodes":[{"type":"text","children":[{"type":"sub_text"}]}]}"# + ); +} + +#[test] +fn operation_update_node_attributes_serde_test() { + let operation = NodeOperation::Update { + path: Path(vec![0, 1]), + changeset: Changeset::Attributes { + new: AttributeBuilder::new().insert("bold", true).build(), + old: AttributeBuilder::new().insert("bold", false).build(), + }, + }; + + let result = serde_json::to_string(&operation).unwrap(); + assert_eq!( + result, + r#"{"op":"update","path":[0,1],"changeset":{"attributes":{"new":{"bold":true},"old":{"bold":false}}}}"# + ); +} + +#[test] +fn operation_update_node_body_serialize_test() { + let delta = DeltaTextOperationBuilder::new().insert("AppFlowy...").build(); + let inverted = delta.invert_str(""); + let changeset = Changeset::Delta { delta, inverted }; + let insert = NodeOperation::Update { + path: Path(vec![0, 1]), + changeset, + }; + let result = serde_json::to_string(&insert).unwrap(); + assert_eq!( + result, + r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"# + ); +} + +#[test] +fn operation_update_node_body_deserialize_test() { + let json_1 = r#"{"op":"update","path":[0,1],"changeset":{"delta":{"delta":[{"insert":"AppFlowy..."}],"inverted":[{"delete":11}]}}}"#; + let operation: NodeOperation = serde_json::from_str(json_1).unwrap(); + let json_2 = serde_json::to_string(&operation).unwrap(); + assert_eq!(json_1, json_2); +} + +// #[test] +// fn transaction_serialize_test() { +// let insert = NodeOperation::Insert { +// path: Path(vec![0, 1]), +// nodes: vec![NodeData::new("text".to_owned())], +// }; +// let transaction = Transaction::from_operations(vec![insert]); +// let json = serde_json::to_string(&transaction).unwrap(); +// assert_eq!( +// json, +// r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}]}"# +// ); +// } +// +// #[test] +// fn transaction_deserialize_test() { +// let json = r#"{"operations":[{"op":"insert","path":[0,1],"nodes":[{"type":"text"}]}],"TextSelection":{"before_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}},"after_selection":{"start":{"path":[],"offset":0},"end":{"path":[],"offset":0}}}}"#; +// +// let transaction: Transaction = serde_json::from_str(json).unwrap(); +// assert_eq!(transaction.operations.len(), 1); +// } +// +// #[test] +// fn node_tree_deserialize_test() { +// let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); +// assert_eq!(tree.number_of_children(None), 1); +// } + +#[test] +fn node_tree_serialize_test() { + let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); + let json = serde_json::to_string_pretty(&tree).unwrap(); + assert_eq!(json, TREE_JSON); +} + +#[test] +fn node_tree_serde_test() { + let tree: NodeTree = serde_json::from_str(TREE_JSON).unwrap(); + let bytes = tree.to_bytes(); + let tree = NodeTree::from_bytes(&bytes).unwrap(); + assert_eq!(bytes, tree.to_bytes()); +} + +#[allow(dead_code)] +const TREE_JSON: &str = r#"{ + "type": "editor", + "children": [ + { + "type": "image", + "attributes": { + "image_src": "https://s1.ax1x.com/2022/08/26/v2sSbR.jpg" + } + }, + { + "type": "text", + "attributes": { + "heading": "h1" + }, + "body": { + "delta": [ + { + "insert": "👋 " + }, + { + "insert": "Welcome to ", + "attributes": { + "href": "appflowy.io" + } + }, + { + "insert": "AppFlowy Editor", + "attributes": { + "italic": true + } + } + ] + } + } + ] +}"#; diff --git a/shared-lib/lib-ot/tests/node/transaction_compose_test.rs b/shared-lib/lib-ot/tests/node/transaction_compose_test.rs new file mode 100644 index 0000000000..4b5dd11b76 --- /dev/null +++ b/shared-lib/lib-ot/tests/node/transaction_compose_test.rs @@ -0,0 +1,104 @@ +use crate::node::script::{edit_node_delta, make_node_delta_changeset}; +use lib_ot::core::{AttributeEntry, Changeset, NodeDataBuilder, NodeOperation, Transaction, TransactionBuilder}; +use lib_ot::text_delta::DeltaTextOperationBuilder; + +#[test] +fn transaction_compose_update_after_insert_test() { + let (initial_delta, changeset, _) = make_node_delta_changeset("Hello", " world"); + let node_data = NodeDataBuilder::new("text").insert_delta(initial_delta).build(); + + // Modify the same path, the operations will be merged after composing if possible. + let mut transaction_a = TransactionBuilder::new().insert_node_at_path(0, node_data).build(); + let transaction_b = TransactionBuilder::new().update_node_at_path(0, changeset).build(); + let _ = transaction_a.compose(transaction_b).unwrap(); + + // The operations are merged into one operation + assert_eq!(transaction_a.operations.len(), 1); + assert_eq!( + transaction_a.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world"}]}}]}]}"# + ); +} + +#[test] +fn transaction_compose_multiple_update_test() { + let (initial_delta, changeset_1, final_delta) = make_node_delta_changeset("Hello", " world"); + let mut transaction = TransactionBuilder::new() + .insert_node_at_path(0, NodeDataBuilder::new("text").insert_delta(initial_delta).build()) + .build(); + let (changeset_2, _) = edit_node_delta( + &final_delta, + DeltaTextOperationBuilder::new() + .retain(final_delta.utf16_target_len) + .insert("😁") + .build(), + ); + + let mut other_transaction = Transaction::new(); + + // the following two update operations will be merged into one + let update_1 = TransactionBuilder::new().update_node_at_path(0, changeset_1).build(); + other_transaction.compose(update_1).unwrap(); + + let update_2 = TransactionBuilder::new().update_node_at_path(0, changeset_2).build(); + other_transaction.compose(update_2).unwrap(); + + let inverted = Transaction::from_operations(other_transaction.operations.inverted()); + + // the update operation will be merged into insert operation + let _ = transaction.compose(other_transaction).unwrap(); + assert_eq!(transaction.operations.len(), 1); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello world😁"}]}}]}]}"# + ); + + let _ = transaction.compose(inverted).unwrap(); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]}]}"# + ); +} + +#[test] +fn transaction_compose_multiple_attribute_test() { + let delta = DeltaTextOperationBuilder::new().insert("Hello").build(); + let node = NodeDataBuilder::new("text").insert_delta(delta).build(); + + let insert_operation = NodeOperation::Insert { + path: 0.into(), + nodes: vec![node], + }; + + let mut transaction = Transaction::new(); + transaction.push_operation(insert_operation); + + let new_attribute = AttributeEntry::new("subtype", "bulleted-list"); + let update_operation = NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: new_attribute.clone().into(), + old: Default::default(), + }, + }; + transaction.push_operation(update_operation); + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"bulleted-list"},"old":{}}}}]}"# + ); + + let old_attribute = new_attribute; + let new_attribute = AttributeEntry::new("subtype", "number-list"); + transaction.push_operation(NodeOperation::Update { + path: 0.into(), + changeset: Changeset::Attributes { + new: new_attribute.into(), + old: old_attribute.into(), + }, + }); + + assert_eq!( + transaction.to_json().unwrap(), + r#"{"operations":[{"op":"insert","path":[0],"nodes":[{"type":"text","body":{"delta":[{"insert":"Hello"}]}}]},{"op":"update","path":[0],"changeset":{"attributes":{"new":{"subtype":"number-list"},"old":{"subtype":"bulleted-list"}}}}]}"# + ); +} diff --git a/shared-lib/lib-ot/tests/node/tree_test.rs b/shared-lib/lib-ot/tests/node/tree_test.rs index 98f9ee00ed..0606b88cc9 100644 --- a/shared-lib/lib-ot/tests/node/tree_test.rs +++ b/shared-lib/lib-ot/tests/node/tree_test.rs @@ -1,25 +1,101 @@ use crate::node::script::NodeScript::*; -use crate::node::script::NodeTest; -use lib_ot::core::NodeBody; -use lib_ot::core::NodeBodyChangeset; -use lib_ot::core::OperationTransform; +use crate::node::script::{make_node_delta_changeset, NodeTest}; + use lib_ot::core::{NodeData, NodeDataBuilder, Path}; -use lib_ot::text_delta::TextOperationBuilder; #[test] fn node_insert_test() { let mut test = NodeTest::new(); - let inserted_node = NodeData::new("text"); - let path: Path = 0.into(); + let node_data = NodeData::new("text"); + let path: Path = vec![0].into(); let scripts = vec![ InsertNode { path: path.clone(), - node_data: inserted_node.clone(), + node_data: node_data.clone(), rev_id: 1, }, - AssertNodeData { + AssertNode { path, - expected: Some(inserted_node), + expected: Some(node_data), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +#[should_panic] +fn node_insert_with_empty_path_test() { + let mut test = NodeTest::new(); + let scripts = vec![InsertNode { + path: vec![].into(), + node_data: NodeData::new("text"), + rev_id: 1, + }]; + test.run_scripts(scripts); +} + +#[test] +fn tree_insert_multiple_nodes_at_root_path_test() { + let mut test = NodeTest::new(); + let node_1 = NodeData::new("a"); + let node_2 = NodeData::new("b"); + let node_3 = NodeData::new("c"); + let node_data_list = vec![node_1, node_2, node_3]; + let path: Path = vec![0].into(); + + // Insert three nodes under the root + let scripts = vec![ + // 0:a + // 1:b + // 2:c + InsertNodes { + path, + node_data_list: node_data_list.clone(), + rev_id: 1, + }, + AssertNodesAtRoot { + expected: node_data_list, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn tree_insert_multiple_nodes_at_root_path_test2() { + let mut test = NodeTest::new(); + let node_1 = NodeData::new("a"); + let node_2 = NodeData::new("b"); + let node_3 = NodeData::new("c"); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: node_2.clone(), + rev_id: 2, + }, + InsertNode { + path: 2.into(), + node_data: node_3.clone(), + rev_id: 3, + }, + // 0:a + // 1:b + // 2:c + AssertNode { + path: 0.into(), + expected: Some(node_1), + }, + AssertNode { + path: 1.into(), + expected: Some(node_2), + }, + AssertNode { + path: 2.into(), + expected: Some(node_3), }, ]; test.run_scripts(scripts); @@ -28,61 +104,40 @@ fn node_insert_test() { #[test] fn node_insert_node_with_children_test() { let mut test = NodeTest::new(); - let inserted_node = NodeDataBuilder::new("text").add_node(NodeData::new("image")).build(); + let image_1 = NodeData::new("image_a"); + let image_2 = NodeData::new("image_b"); + + let image = NodeDataBuilder::new("image") + .add_node_data(image_1.clone()) + .add_node_data(image_2.clone()) + .build(); + let node_data = NodeDataBuilder::new("text").add_node_data(image.clone()).build(); let path: Path = 0.into(); let scripts = vec![ InsertNode { path: path.clone(), - node_data: inserted_node.clone(), + node_data: node_data.clone(), rev_id: 1, }, - AssertNodeData { + // 0:text + // 0:image + // 0:image_1 + // 1:image_2 + AssertNode { path, - expected: Some(inserted_node), + expected: Some(node_data), }, - ]; - test.run_scripts(scripts); -} - -#[test] -fn node_insert_multi_nodes_test() { - let mut test = NodeTest::new(); - let path_1: Path = 0.into(); - let node_1 = NodeData::new("text_1"); - - let path_2: Path = 1.into(); - let node_2 = NodeData::new("text_2"); - - let path_3: Path = 2.into(); - let node_3 = NodeData::new("text_3"); - - let scripts = vec![ - InsertNode { - path: path_1.clone(), - node_data: node_1.clone(), - rev_id: 1, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image), }, - InsertNode { - path: path_2.clone(), - node_data: node_2.clone(), - rev_id: 2, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_1), }, - InsertNode { - path: path_3.clone(), - node_data: node_3.clone(), - rev_id: 3, - }, - AssertNodeData { - path: path_1, - expected: Some(node_1), - }, - AssertNodeData { - path: path_2, - expected: Some(node_2), - }, - AssertNodeData { - path: path_3, - expected: Some(node_3), + AssertNode { + path: vec![0, 0, 1].into(), + expected: Some(image_2), }, ]; test.run_scripts(scripts); @@ -129,19 +184,22 @@ fn node_insert_node_in_ordered_nodes_test() { // 1:text_2_2 // 2:text_2_1 // 3:text_3 - AssertNodeData { + AssertNode { path: path_1, expected: Some(node_1), }, - AssertNodeData { + AssertNode { path: path_2, expected: Some(node_2_2), }, - AssertNodeData { + AssertNode { path: path_3, expected: Some(node_2_1), }, - AssertNumberOfNodesAtPath { path: None, len: 4 }, + AssertNumberOfChildrenAtPath { + path: None, + expected: 4, + }, ]; test.run_scripts(scripts); } @@ -152,15 +210,15 @@ fn node_insert_nested_nodes_test() { let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); let node_data_1 = NodeDataBuilder::new("text_1") - .add_node(node_data_1_1.clone()) - .add_node(node_data_1_2.clone()) + .add_node_data(node_data_1_1.clone()) + .add_node_data(node_data_1_2.clone()) .build(); let node_data_2_1 = NodeDataBuilder::new("text_2_1").build(); let node_data_2_2 = NodeDataBuilder::new("text_2_2").build(); let node_data_2 = NodeDataBuilder::new("text_2") - .add_node(node_data_2_1.clone()) - .add_node(node_data_2_2.clone()) + .add_node_data(node_data_2_1.clone()) + .add_node_data(node_data_2_2.clone()) .build(); let scripts = vec![ @@ -183,19 +241,19 @@ fn node_insert_nested_nodes_test() { // 1:text_2_2 AssertNode { path: vec![0, 0].into(), - expected: Some(node_data_1_1.into()), + expected: Some(node_data_1_1), }, AssertNode { path: vec![0, 1].into(), - expected: Some(node_data_1_2.into()), + expected: Some(node_data_1_2), }, AssertNode { path: vec![1, 0].into(), - expected: Some(node_data_2_1.into()), + expected: Some(node_data_2_1), }, AssertNode { path: vec![1, 1].into(), - expected: Some(node_data_2_2.into()), + expected: Some(node_data_2_2), }, ]; test.run_scripts(scripts); @@ -207,8 +265,8 @@ fn node_insert_node_before_existing_nested_nodes_test() { let node_data_1_1 = NodeDataBuilder::new("text_1_1").build(); let node_data_1_2 = NodeDataBuilder::new("text_1_2").build(); let node_data_1 = NodeDataBuilder::new("text_1") - .add_node(node_data_1_1.clone()) - .add_node(node_data_1_2.clone()) + .add_node_data(node_data_1_1.clone()) + .add_node_data(node_data_1_2.clone()) .build(); let scripts = vec![ @@ -231,11 +289,11 @@ fn node_insert_node_before_existing_nested_nodes_test() { // 1:text_1_2 AssertNode { path: vec![1, 0].into(), - expected: Some(node_data_1_1.into()), + expected: Some(node_data_1_1), }, AssertNode { path: vec![1, 1].into(), - expected: Some(node_data_1_2.into()), + expected: Some(node_data_1_2), }, ]; test.run_scripts(scripts); @@ -258,7 +316,7 @@ fn node_insert_with_attributes_test() { path: path.clone(), attributes: inserted_node.attributes.clone(), }, - AssertNodeData { + AssertNode { path, expected: Some(inserted_node), }, @@ -270,7 +328,6 @@ fn node_insert_with_attributes_test() { fn node_delete_test() { let mut test = NodeTest::new(); let inserted_node = NodeData::new("text"); - let path: Path = 0.into(); let scripts = vec![ InsertNode { @@ -282,7 +339,318 @@ fn node_delete_test() { path: path.clone(), rev_id: 2, }, - AssertNodeData { path, expected: None }, + AssertNode { path, expected: None }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_delete_node_from_list_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1).build(); + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a) + .add_node_data(image_b) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2.clone(), + rev_id: 2, + }, + DeleteNode { + path: 0.into(), + rev_id: 3, + }, + AssertNode { + path: 1.into(), + expected: None, + }, + AssertNode { + path: 0.into(), + expected: Some(text_node_2), + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image_2), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_delete_nested_node_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1).build(); + + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + InsertNode { + path: 1.into(), + node_data: text_node_2, + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 3, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b.clone()), + }, + DeleteNode { + path: vec![0, 0].into(), + rev_id: 4, + }, + // 0:text_1 + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + AssertNumberOfChildrenAtPath { + path: Some(0.into()), + expected: 0, + }, + AssertNode { + path: vec![0].into(), + expected: Some(NodeDataBuilder::new("text_1").build()), + }, + AssertNode { + path: vec![1, 0, 0].into(), + expected: Some(image_a), + }, + AssertNode { + path: vec![1, 0, 1].into(), + expected: Some(image_b), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_delete_children_test() { + let mut test = NodeTest::new(); + let inserted_node = NodeDataBuilder::new("text") + .add_node_data(NodeDataBuilder::new("sub_text_1").build()) + .add_node_data(NodeDataBuilder::new("sub_text_2").build()) + .add_node_data(NodeDataBuilder::new("sub_text_3").build()) + .build(); + + let scripts = vec![ + InsertNode { + path: vec![0].into(), + node_data: inserted_node, + rev_id: 1, + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(NodeDataBuilder::new("sub_text_1").build()), + }, + AssertNode { + path: vec![0, 1].into(), + expected: Some(NodeDataBuilder::new("sub_text_2").build()), + }, + AssertNode { + path: vec![0, 2].into(), + expected: Some(NodeDataBuilder::new("sub_text_3").build()), + }, + AssertNumberOfChildrenAtPath { + path: Some(Path(vec![0])), + expected: 3, + }, + DeleteNode { + path: vec![0, 0].into(), + rev_id: 2, + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(NodeDataBuilder::new("sub_text_2").build()), + }, + AssertNumberOfChildrenAtPath { + path: Some(Path(vec![0])), + expected: 2, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_reorder_sub_nodes_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + + let child_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(child_1).build(); + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1, + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0, 0, 0].into(), + rev_id: 2, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + InsertNode { + path: vec![0, 0, 1].into(), + node_data: image_a.clone(), + rev_id: 3, + }, + // 0:text_1 + // 0:image_1 + // 0:image_b + // 1:image_a + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_b), + }, + AssertNode { + path: vec![0, 0, 1].into(), + expected: Some(image_a), + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_reorder_nodes_test() { + let mut test = NodeTest::new(); + let image_a = NodeData::new("image_a"); + let image_b = NodeData::new("image_b"); + + let image_1 = NodeDataBuilder::new("image_1") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_1 = NodeDataBuilder::new("text_1").add_node_data(image_1.clone()).build(); + + let image_2 = NodeDataBuilder::new("image_2") + .add_node_data(image_a.clone()) + .add_node_data(image_b.clone()) + .build(); + let text_node_2 = NodeDataBuilder::new("text_2").add_node_data(image_2.clone()).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: text_node_1.clone(), + rev_id: 1, + }, + InsertNode { + path: 0.into(), + node_data: text_node_2.clone(), + rev_id: 1, + }, + // 0:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + // 1:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + DeleteNode { + path: vec![0].into(), + rev_id: 3, + }, + AssertNode { + path: vec![0].into(), + expected: Some(text_node_2.clone()), + }, + InsertNode { + path: vec![1].into(), + node_data: text_node_1.clone(), + rev_id: 4, + }, + // 0:text_2 + // 0:image_2 + // 0:image_a + // 1:image_b + // 1:text_1 + // 0:image_1 + // 0:image_a + // 1:image_b + AssertNode { + path: vec![0].into(), + expected: Some(text_node_2), + }, + AssertNode { + path: vec![0, 0].into(), + expected: Some(image_2), + }, + AssertNode { + path: vec![0, 0, 0].into(), + expected: Some(image_a), + }, + AssertNode { + path: vec![1].into(), + expected: Some(text_node_1), + }, + AssertNode { + path: vec![1, 0].into(), + expected: Some(image_1), + }, + AssertNode { + path: vec![1, 0, 1].into(), + expected: Some(image_b), + }, ]; test.run_scripts(scripts); } @@ -290,29 +658,51 @@ fn node_delete_test() { #[test] fn node_update_body_test() { let mut test = NodeTest::new(); - let path: Path = 0.into(); - - let s = "Hello".to_owned(); - let init_delta = TextOperationBuilder::new().insert(&s).build(); - let delta = TextOperationBuilder::new().retain(s.len()).insert(" AppFlowy").build(); - let inverted = delta.invert(&init_delta); - let expected = init_delta.compose(&delta).unwrap(); - - let node = NodeDataBuilder::new("text") - .insert_body(NodeBody::Delta(init_delta)) - .build(); + let (initial_delta, changeset, expected) = make_node_delta_changeset("Hello", "AppFlowy"); + let node = NodeDataBuilder::new("text").insert_delta(initial_delta).build(); let scripts = vec![ InsertNode { - path: path.clone(), + path: 0.into(), node_data: node, rev_id: 1, }, UpdateBody { - path: path.clone(), - changeset: NodeBodyChangeset::Delta { delta, inverted }, + path: 0.into(), + changeset, + }, + AssertNodeDelta { + path: 0.into(), + expected, + }, + ]; + test.run_scripts(scripts); +} + +#[test] +fn node_inverted_body_changeset_test() { + let mut test = NodeTest::new(); + let (initial_delta, changeset, _expected) = make_node_delta_changeset("Hello", "AppFlowy"); + let node = NodeDataBuilder::new("text").insert_delta(initial_delta.clone()).build(); + + let scripts = vec![ + InsertNode { + path: 0.into(), + node_data: node, + rev_id: 1, + }, + UpdateBody { + path: 0.into(), + changeset: changeset.clone(), + }, + UpdateBody { + path: 0.into(), + changeset: changeset.inverted(), + }, + AssertNodeDelta { + path: 0.into(), + expected: initial_delta, }, - AssertNodeDelta { path, expected }, ]; test.run_scripts(scripts); } diff --git a/shared-lib/lib-ws/Cargo.toml b/shared-lib/lib-ws/Cargo.toml index 003bdf43bd..3e49545e6c 100644 --- a/shared-lib/lib-ws/Cargo.toml +++ b/shared-lib/lib-ws/Cargo.toml @@ -24,11 +24,11 @@ tracing = { version = "0.1", features = ["log"] } protobuf = {version = "2.18.0"} strum = "0.21" strum_macros = "0.21" -parking_lot = "0.11" +parking_lot = "0.12.1" dashmap = "5" [build-dependencies] -lib-infra = { path = "../lib-infra", features = ["protobuf_file_gen"] } +lib-infra = { path = "../lib-infra", features = ["proto_gen"] } [dev-dependencies] tokio = {version = "1", features = ["full"]}