Compare commits

..
959 Commits
Author SHA1 Message Date
Nathan 4354227a05 chore: enable select source when user is not anon 2025-05-07 14:31:08 +08:00
Nathan.fooo f7b556677f Custom langchain rust (#7887)
* chore: implement custom memory store chain

* chore: do not allow edit embedding model
2025-05-07 13:37:44 +08:00
Lucas 19b1bccb03 chore: upgrade editor version (#7886) 2025-05-07 11:03:47 +08:00
Nathan.fooo 0f5699571c chore: filter out space when indexing search (#7882) 2025-05-06 20:49:44 +08:00
Nathan.fooo 53cbd22111 chore: local ai chat with pages (#7881)
* chore: local ai chat with page

* chore: temp

* chore: clippy

* chore: fix ui bugs

* chore: fix ui bugs

* chore: fix view title

* chore: cloud id

* chore: bump server

* chore: bump server
2025-05-06 20:32:10 +08:00
Lucas 926a5b31bb refacotr: ai chat page folder strcture (#7879)
* refacotr: ai chat page folder strcture

* feat: add non-reversed animated chat list

* chore: add scroll animation

* feat: add fade animation

* fix: refresh issue

* feat: add flag to control the ai chat animation

* chore: enable reversed list
2025-05-06 13:10:14 +08:00
Nathan.fooo bdac73e934 chore: refactor local ai (#7880)
* chore: refactor local ai

* chore: ios build
2025-05-05 20:27:29 +08:00
Nathan.fooo 20f3116bd9 refactor: Model select (#7875)
* refactor: model select

* refactor: add test

* fix: add source

* fix: add source

* chore: notify all unset source

* chore: fix test
2025-05-02 15:37:40 +08:00
Lucas a2b1f8c325 chore: pick commits from release/0.9.1 (#7874)
* chore: update i18n

* chore: remove sentry

* fix: allow the member to get the workspace usage

* fix: integration test
2025-05-02 14:48:26 +08:00
Nathan.fooo 612d652320 feat: support local ai embedding, local ai search, local document content search (#7839)
* chore: generate embeddings

* chore: save embedding

* chore: vec sqlite

* chore: clippy

* chore: init vector

* chore: create vector db

* chore: periodically write embedding

* chore: fix compile

* chore: skip write

* chore: impl search

* fix: test

* fix: stop scheduler

* fix: search

* chore: add test

* chore: update schema

* chore: index all

* chore: index

* chore: search document content

* chore: index document content and title

* chore: index all view

* chore: rename trait

* refactor: remove folder index manager

* chore: index folder changes

* chore: delete folder search

* chore: update logs

* chore: update logs

* chore: search

* chore: add search test

* chore: fmt

* chore: fix test

* chore: fix ios build
2025-05-02 08:21:42 +08:00
Nathan.foooandLucas.Xu 7750f2257f fix: anon user can not locate its data (#7873)
* fix: anon user can not locate its data

* chore: add test

* chore: bump version 0.9.1

* chore: remove self-hosted ios ci

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-05-01 15:06:56 +08:00
Lucas e6e23936f5 fix: appimage build (#7870) 2025-04-30 20:35:10 +08:00
Lucas 8441a8c7f0 feat: remove reminder in the same event (#7869) 2025-04-30 19:34:43 +08:00
Morn 86872c462f chore: update CHANGELOG.md (#7868) 2025-04-30 17:36:56 +08:00
Lucas 9389034bf0 chore: bump editor version e56746a (#7866)
* chore: bump editor version e56746a

* fix: use old workspaces if the get workspaces api failed
2025-04-30 17:18:51 +08:00
Lucas 63244e0b3c fix: paste issue with the nested image (#7857)
* chore: replace delete button style

* chore: replace textfield in settings page

* fix: icon text align issue in database

* fix: member count is 0

* fix: invite member issue on mobile

* fix: checklist item align issue on Windows

* fix: integration test

* fix: support converting paragraph to markdown

* chore: update editor version

* fix: delete command in simple table cell

* feat: use new password error code
2025-04-30 11:45:48 +08:00
Morn 64dff44ce1 fix: revert some date picker code and fix notification issue (#7861)
* fix: revert some date picker code and fix notification issue

* fix: UI overflow issue

* chore: update reminder remove logic
2025-04-30 11:27:57 +08:00
Richard Shiue 8b1eb49155 chore: import new prompts and add featured category (#7859)
* chore: mark prompts as featured

* chore: implement is featured list

* chore: implement categories

* chore: remove learning category
2025-04-30 09:49:13 +08:00
Richard Shiue 4a4ee79c3f fix: adjust chat message layout (#7860)
* fix: user chat message bubble should have flex

* chore: adjust spacing between messages
2025-04-29 20:42:19 +08:00
Richard Shiue 22111cfa53 fix: text color for local ai model picker (#7855) 2025-04-29 18:07:25 +08:00
Morn d28987d16d fix: some mobile search UI issues (#7845)
* fix: some mobile search UI issues

* fix: some icon colors

* fix: show 'Add Link' while creating a link on mobile

* fix: add tapping event for mobile embed link

* fix: bookmark can not open the non-http link

* fix: improve the behavior of links on mobile

* fix: switch workspace will refresh the reminders

* chore: remove unused import

* fix: test error
2025-04-29 12:52:28 +08:00
Lucas 63aa5112ba fix: integration test (#7852) 2025-04-29 11:20:42 +08:00
Morn 4f3eefbefc chore: remove unuseful import (#7849) 2025-04-29 09:58:21 +08:00
MornandLucas.Xu 9353863439 fix: some lr issues (#7848)
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-04-28 22:13:13 +08:00
Lucas fb33e4d5c3 fix: unable to expand sidebar on Windows (#7847)
* fix: unable to collapse sidebar

* fix: unable to expand workspace menu
2025-04-28 22:01:37 +08:00
Lucas 807aa74509 chore: update i18n (#7846)
* chore: update i18n

* chore: update i18n

* chore: update i18n

* chore: only show one copied toast

* chore: add upgrade dialog in invite link

* chore: update dialog style

* fix: flutter tests
2025-04-28 20:47:08 +08:00
Lucas 168b29abe3 Revert "chore: try to fix the mobile build" (#7844)
This reverts commit 3f06f6415a.
2025-04-28 13:25:05 +08:00
Nathan 2463107c6c chore: fix ios build 2025-04-28 13:18:18 +08:00
Richard Shiue 2b03c34df1 fix: local and remote returning different data (#7843) 2025-04-28 13:17:16 +08:00
Morn 66fac524c1 fix: some notification center launch review issues (#7825)
* fix: some notification center UI issues

* fix: add number to add dot on mobile

* chore: revamp number red dot

* fix: apply tokens for notification center

* fix: change some token value

* chore: change notification at to icon

* fix: revamp reminder bloc

* fix: test errors

* chore: lint issue

* fix: unit test errors
2025-04-28 13:16:52 +08:00
Lucas 2661ad27ec feat: improve password error message (#7842)
* chore: update i18n

* chore: bump client api version

* chore: try to fix the mobile build

* chore: enable iOS CI
2025-04-28 13:08:28 +08:00
Morn 15bfdfb550 fix: some mobile search LR issues (#7836) 2025-04-28 11:16:02 +08:00
Morn 1f54946a0e fix: mobile link LR issues (#7837)
* fix: mobile link LR issues

* fix: change mobile toolbar link icon
2025-04-28 11:14:23 +08:00
Nathan.fooo f4d7d1381d Merge pull request #7840 from AppFlowy-IO/fix_dropdown_menu
chore: remove radius on dropdown menu
2025-04-28 10:34:54 +08:00
Lucas ae5f06ad1a feat: support 'forgot password', 'setup password' and 'change password' on mobile (#7841)
* feat: forgot password

* fix: warning

* feat: move verify token logic to sign in bloc

* feat: set new page page

* feat: integrate reset password api

* chore: i18n

* chore: upgrade iOS podfile

* feat: support setup password and change password on mobile

* chore: remove unused files

* chore: i18n

* fix: flutter analyze
2025-04-28 10:25:07 +08:00
Richard Shiue ecb1c07917 chore: new design for custom prompt (#7833)
* chore: new design

* chore: improve text selection bg

* fix: remove listener
2025-04-28 10:03:06 +08:00
Richard Shiue cc982b81ee fix: custom prompt launch review issues (#7829)
* fix: text overflow in category items

* fix: don't pass invalid characters

* fix: prompt title typo

* fix: examples should also rcontain initial instructions

* fix: selection not visible

* fix: chat page input border gets cut off

* fix: remove hover and stretch buttons
2025-04-28 09:04:29 +08:00
Nathan 90bbaea2c2 chore: remove radius on dropdown menu 2025-04-27 19:11:05 +08:00
Nathan.fooo ec5eb4e337 Merge pull request #7834 from AppFlowy-IO/support_switch_local_models
Support switch local models
2025-04-26 10:43:59 +08:00
Nathan 28a4303f0c Merge branch 'main' into support_switch_local_models 2025-04-26 10:43:32 +08:00
Nathan f374ca1574 chore: update 2025-04-26 10:40:33 +08:00
Nathan 549e8aee03 chore: clippy 2025-04-26 10:04:03 +08:00
Nathan 3bc0cc7b43 chore: rename 2025-04-26 00:56:45 +08:00
Nathan 90000add22 chore: update ui 2025-04-25 23:41:35 +08:00
Nathan 86e6845302 chore: update ui 2025-04-25 23:29:22 +08:00
Lucas 670023b8da fix: add open workspace assertion (#7824)
* fix: add fix workspace assertion

* fix: macos build

* feat: add new token

* feat: change min width of AFButton to 76.0

* chore: update translations

* feat: add verifying button

* feat: set barrierDismissible as false

* chore: install libcurl4-openssl-dev on Linux

* fix: flutter analyze

* chore: bump cloud version to 0.9.45

* fix: ci tests

* fix: home bloc test

* fix: integration test

* fix: integration test

* fix: integration test
2025-04-25 21:40:21 +08:00
Nathan 7dd8d06c85 chore: display all local models 2025-04-25 16:53:32 +08:00
Nathan 2c6253576f chore: fix test 2025-04-25 14:33:09 +08:00
Nathan.fooo 7f13790485 Merge pull request #7828 from AppFlowy-IO/fix_user_rename
fix: user name stay no change when reopen the setting menu after renaming
2025-04-25 14:02:22 +08:00
Nathan 3bd34226c3 chore: refresh user profile when open workspace 2025-04-25 14:01:06 +08:00
Nathan d893b89e4d fix: clippy 2025-04-25 13:13:08 +08:00
Nathan.fooo d0b3a0dda3 Merge pull request #7660 from AppFlowy-IO/ai_writer_test
Ai writer test
2025-04-25 11:18:51 +08:00
Nathan 4d2380a954 fix: test 2025-04-25 10:35:09 +08:00
Nathan a673b12514 fix: test 2025-04-25 00:16:04 +08:00
Nathan 14fc72bac6 fix: test 2025-04-24 23:53:43 +08:00
Nathan f0875d6b6e fix: user rename 2025-04-24 23:29:20 +08:00
Nathan.fooo f822743c1b Merge pull request #7826 from AppFlowy-IO/workspace_type2
chore: replace auth type with workspace type
2025-04-24 20:58:53 +08:00
Nathan 36dacdbdf2 chore: fix test 2025-04-24 20:55:06 +08:00
Nathan 8485a67357 chore: clippy 2025-04-24 20:08:25 +08:00
Nathan da47c11070 chore: replace auth type with workspace type 2025-04-24 20:01:41 +08:00
Nathan.fooo c35609b1c3 Merge pull request #7823 from AppFlowy-IO/use_user_auth_type
chore: use user auth type
2025-04-24 16:32:23 +08:00
Nathan dbda1972d1 chore: update client api 2025-04-24 16:25:42 +08:00
Nathan c9ca6c6cc8 chore: update template 2025-04-24 15:54:17 +08:00
Nathan 9e5a6df52c chore: update template 2025-04-24 15:45:38 +08:00
Nathan 76b9cbd446 Merge branch 'main' into use_user_auth_type 2025-04-24 13:39:54 +08:00
Nathan 718c5ad16f chore: use auth type instead of workspace auth type 2025-04-24 13:38:25 +08:00
Lucas 48d6a71d96 refactor: deeplink handler (#7802)
* refactor: deeplink handler

* feat: add invitation deeplink handler

* test: add deeplink tests

* feat: add user_id in the invitation callback

* feat: add expire login deeplink handler

* feat: add loading indicator in continue with password page

* feat: replace user_id with email

* fix: invitation deeplink test
2025-04-24 13:20:05 +08:00
Lucas 6a887fdca9 feat: support invite member by link on mobile (#7811)
* fix: invitation link issues

* fix: cargo fmt

* feat: support invite member by link on mobile

* feat: implement new settings page design on mobile

* feat: add leave workspace button

* fix: flutter analyze

* fix: bloc error

* feat: add addMembers page
2025-04-24 10:28:18 +08:00
Nathan 851869644b Merge branch 'main' into ai_writer_test 2025-04-24 10:25:04 +08:00
Richard Shiue 780ba51d87 chore: remove sample response (#7821) 2025-04-24 10:13:38 +08:00
Nathan.fooo 276e6503f7 Merge pull request #7816 from AppFlowy-IO/disable_migrate_anono
chore: disable migrate anon user
2025-04-24 09:59:58 +08:00
Nathan 4f23139df0 chore: remove test 2025-04-24 00:33:19 +08:00
Richard Shiue f83375b002 chore: hide appflowy avatar in ai chat (#7817) 2025-04-23 23:32:52 +08:00
Richard Shiue 77b7912aed feat: built-in custom prompts (#7805)
* feat: custom prompts

* chore: add new prompts

* chore: hover to preview

* chore: enable in ai writer as well
2025-04-23 23:32:40 +08:00
Nathan 291607522b chore: disable migrate anon user 2025-04-23 23:01:52 +08:00
Lucas 4e78b994b5 fix: windows frameless issue (#7813) 2025-04-23 22:09:27 +08:00
Nathan.fooo 288e034ec0 Merge pull request #7814 from AppFlowy-IO/user_awareness_init
chore: init user awareness
2025-04-23 22:08:16 +08:00
Nathan 480eaf1cf5 chore: init user awareness 2025-04-23 20:46:01 +08:00
Nathan.fooo d3adfe50e1 Merge pull request #7812 from AppFlowy-IO/fix_potential_open_rocksdb_error
chore: fix potential open rocksdb for the same user
2025-04-23 17:25:00 +08:00
Nathan 14d51f9123 chore: fmt 2025-04-23 17:24:17 +08:00
Nathan b19b7bbbf9 fix: disable flutter log on test 2025-04-23 16:55:40 +08:00
Nathan 7d15094f9c fix: test 2025-04-23 16:50:05 +08:00
Nathan 914a89b66c Merge branch 'main' into fix_potential_open_rocksdb_error 2025-04-23 16:32:50 +08:00
Nathan 26466f5a6f fix: create workspace 2025-04-23 16:30:07 +08:00
Nathan 7fc2fd56f0 fix: retain cycle 2025-04-23 15:59:53 +08:00
Lucas 1977cf6637 feat: display joined at and user avatar in members page (#7809)
* feat: new settings page design

* feat: support display member join at and avatar

* chore: bump version 0.9.0

* chore: update translations
2025-04-23 13:41:52 +08:00
Morn 4626c18ced fix: revamp desktop notification center (#7607)
* fix: revamp desktop notification center

* fix: complete the red dot

* fix: remove context from showToastNotification

* fix: date picker not updated after enable time

* fix: path erro with ActionArgumentKeys.path
2025-04-23 13:41:34 +08:00
Morn 5b2853cca2 feat: support new design for mobile link (#7772)
* feat: support new design for mobile link

* chore: update UI of remove link

* chore: remove LinkUtil and add LinkExtension
2025-04-23 13:40:04 +08:00
Morn 7d63f309c4 chore: update toolbar ai icons (#7792)
* chore: replace toolbar ai icons

* fix: typo
2025-04-23 13:39:46 +08:00
Morn aa4f904767 feat: support mobile search (#7804)
* feat: support anon local ai chat/writer

* feat: add mobile search page

* feat: complete mobile search page

* chore: add some tests for mobile search

* fix: some UI issues
2025-04-23 13:39:16 +08:00
Nathan.fooo 4067eea89f Merge pull request #7806 from AppFlowy-IO/fix_anon_open
chore: fix anon
2025-04-23 13:30:51 +08:00
Nathan d780ac32ee chore: import workspace database id 2025-04-23 11:45:42 +08:00
Nathan 742dee9202 chore: fix display search content area 2025-04-23 11:45:42 +08:00
Lucas a1e2c11d8a fix: windows scaling issue (#7807) 2025-04-23 11:13:07 +08:00
Lucas.Xu 471d2c0b50 fix: outline block test 2025-04-23 10:04:48 +08:00
Lucas.Xu be2b32ffd1 Merge branch 'main' into fix_anon_open 2025-04-23 09:59:10 +08:00
Nathan db5e4766af chore: fix test 2025-04-22 23:20:15 +08:00
Richard Shiue f9f151e89e feat: implement afdivider (#7803) 2025-04-22 22:14:03 +08:00
Nathan 7e88a3897c chore: temp 2025-04-22 20:18:13 +08:00
Nathan 63e43db0c2 chore: fix anon improve writing 2025-04-22 19:53:50 +08:00
Nathan 43c1f02b6f chore: fix anon and enable local ai on slash menu 2025-04-22 17:54:53 +08:00
Nathan 6bdaee3a00 chore: set toggle default value 2025-04-22 14:10:02 +08:00
Nathan.fooo 7f74543125 Merge pull request #7797 from AppFlowy-IO/integrate_workspace_template
Integrate workspace template
2025-04-22 13:35:03 +08:00
Nathan 514eeb8466 chore: init local ai when switching workspace 2025-04-22 12:16:59 +08:00
Nathan 403f343371 chore: delete supabase test 2025-04-22 12:16:59 +08:00
Lucas 8c5547da64 fix: custom font doesn't apply to settings page (#7801)
* fix: custom font doesn't apply to settings page

* chore: update settings page icons

* fix: add try catch in http services
2025-04-22 12:05:53 +08:00
Lucas.Xu eaac387c8d fix: remove space migration 2025-04-22 11:26:08 +08:00
Lucas.Xu 00aad4da47 Merge branch 'integrate_workspace_template' of https://github.com/AppFlowy-IO/AppFlowy into integrate_workspace_template 2025-04-22 11:04:04 +08:00
Lucas.Xu c10c844fa9 Merge branch 'main' into integrate_workspace_template 2025-04-22 10:59:39 +08:00
Nathan 40c1ae1d38 fix: click two times to enable local ai 2025-04-22 10:13:24 +08:00
Lucas 3ae6888fee feat: invite member by link (#7780)
* feat: invite member by link

* feat: add invite by link section

* feat: integrate invite by link and copy invite link

* feat: integrate invite link apis

* feat: add reset link dialog

* feat: support redirect to admin panel

* fix: flutter analyze

* feat: remove expire time

* fix: apply correct color in dark mode

* fix: flutter analyze

* chore: disable theme hotkey test
2025-04-22 09:58:13 +08:00
Lucas 14b5e4e184 feat: add loading indicator in continue to sign in button (#7799) 2025-04-22 09:55:10 +08:00
Nathan 530e076838 chore: update test 2025-04-22 00:09:43 +08:00
Nathan 1be51d6679 chore: clippy 2025-04-21 22:26:53 +08:00
Nathan b0c2b04a2d fix: empty view id 2025-04-21 22:22:03 +08:00
Nathan 4e2990e599 chore: remove local model 2025-04-21 22:02:06 +08:00
Nathan 9cd49c2447 chore: create workspace member 2025-04-21 21:07:38 +08:00
Morn 0cdecee771 fix: canLaunchUrl doesn't work with Flatpak (#7796) 2025-04-21 20:38:52 +08:00
Lucas.Xu 1bcf4e6e8d Revert "fix: left side bar does not reflect right workspace content"
This reverts commit 8cd0442a1f.
2025-04-21 17:20:44 +08:00
Lucas.Xu 343f7e4fd5 Revert "fix: loading exception when switching workspace"
This reverts commit 10a536b1a2.
2025-04-21 17:20:35 +08:00
Nathan 4634b51edf chore: open workspace with workspace auth type 2025-04-21 16:47:40 +08:00
Nathan 8cd0442a1f fix: left side bar does not reflect right workspace content 2025-04-21 16:47:27 +08:00
Nathan 10a536b1a2 fix: loading exception when switching workspace 2025-04-21 16:37:02 +08:00
Nathan.fooo 65b7916a6a Merge pull request #7794 from AppFlowy-IO/optimize_write_user_workspaces
refactor: only notify when user workspaces were changed
2025-04-21 13:09:13 +08:00
Nathan 2cf96a2ce3 chore: fix rust test 2025-04-21 13:07:50 +08:00
Nathan 7885cb80f4 chore: fix rust test 2025-04-21 12:03:01 +08:00
Nathan 1356382524 refactor: only notify when user workspaces were changed 2025-04-21 11:41:58 +08:00
Morn 04407fe8ff fix: issues with displaying mention text (#7773)
* fix: some mention text can not display correctly

* fix: remove the image widget from bookmark if the image url is null
2025-04-21 10:45:27 +08:00
David Woods 3451100b80 chore: bumped device_info_plus to 11.2.2 (#7782)
* bumped device_info_plus to 11.2.2 -- this version avoids the crash that happens when getInfo() is called on Windows in a VM or with Hyper-V active

* chore: bumped device_info_plus to 11.2.2 -- this version avoids the crash that happens when getInfo() is called on Windows in a VM or with Hyper-V active
2025-04-21 10:22:22 +08:00
Morn f8927b1843 fix: crash when trying to delete emoji (#7787)
* fix: emoji picker error on desktop

* fix: test errors
2025-04-21 10:22:02 +08:00
Nathan.fooo c7bf8bb1ba Merge pull request #7789 from AppFlowy-IO/imple_local_unsupprot
chore: implement local unsupported methods
2025-04-20 20:52:27 +08:00
Nathan c6010a6734 chore: fmt 2025-04-20 19:51:12 +08:00
Nathan.foooandsourcery-ai[bot] cf46213e00 chore: Update frontend/rust-lib/flowy-folder/src/manager.rs
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
2025-04-20 19:34:05 +08:00
Nathan 2ee786f351 chore: update logs 2025-04-20 19:32:52 +08:00
Nathan 92d5690bba chore: pass folder init data 2025-04-20 18:07:02 +08:00
Nathan 791a79a234 chore: impl local unspport 2025-04-20 17:29:57 +08:00
Nathan fa798f3ecd chore: workspace usage 2025-04-20 15:54:37 +08:00
Nathan.fooo f72739d98d Merge pull request #7788 from AppFlowy-IO/local_auth_type
refactor: local auth type
2025-04-20 15:17:14 +08:00
Nathan fd581b4453 chore: clippy 2025-04-20 14:37:21 +08:00
Nathan 747a63d452 chore: create user workspace for anon user 2025-04-20 14:34:30 +08:00
Nathan 833a2bf5d6 Merge branch 'main' into local_auth_type 2025-04-20 13:36:18 +08:00
Nathan.fooo 607b7ecd1f Merge pull request #7766 from AppFlowy-IO/private_database_inline_view
chore: remove inline view id reference
2025-04-20 13:35:53 +08:00
Nathan bb5d36402a chore: fix test 2025-04-20 13:35:06 +08:00
Nathan ccd1f5f8e9 chore: revert pod lock 2025-04-20 12:47:55 +08:00
Nathan 2c5f41b580 fix: set auth type 2025-04-20 12:47:14 +08:00
Nathan 2f5b494885 chore: remove authticator pb 2025-04-20 00:48:31 +08:00
Nathan.fooo 58f87b39aa Merge pull request #7785 from AppFlowy-IO/auth_type_per_workspace
refactor: workspace setting and auth type
2025-04-20 00:24:45 +08:00
Nathan d478ecfd41 chore: remove unused code 2025-04-19 23:33:34 +08:00
Nathan 72fc0cce07 chore: move sql 2025-04-19 22:18:15 +08:00
Nathan 81f63bebe6 chore: sql 2025-04-19 21:03:50 +08:00
Nathan 102087537a chore: fmt 2025-04-19 15:50:42 +08:00
Nathan 6dac45172e chore: auth type 2025-04-19 15:34:06 +08:00
Richard Shiue 84952b9056 chore: adjust generate theme script to import subtle colors (#7784) 2025-04-19 14:24:32 +08:00
Nathan e851fba71b chore: clippy 2025-04-19 14:21:22 +08:00
Nathan 3a05a4851f chore: clippy 2025-04-19 14:07:49 +08:00
Nathan edc5710e32 chore: auth type and remove unused code 2025-04-19 14:00:51 +08:00
Richard Shiue 1802792795 Merge pull request #7778 from AppFlowy-IO/feat/custom-prompt
feat: merge develop branch
2025-04-18 21:34:10 +08:00
Richard Shiue 28e89beb43 feat: appflowy theme lerp (#7771)
* feat: implement appflowy theme lerping

* refactor: use theme builder and adjust script

* chore: rename theme data

* chore: add doc comments

* chore: rename function

* chore: don't use theme extension

* chore: use the animated appflowy theme widget

* chore: clean up inherited theme
2025-04-18 21:06:46 +08:00
Richard Shiue 889756ebb0 refactor: use script to generate design tokens (#7751)
* refactor: use script to generate design tokens

* chore: improve code readaility

* refactor: make builder reusable to built in themes

* chore: improve code readability
2025-04-18 21:06:46 +08:00
Richard Shiue 54b5e248e3 feat: implement modal (#7750) 2025-04-18 21:06:46 +08:00
Nathan.fooo d24383b6ea Merge pull request #7779 from AppFlowy-IO/server_type
refactor: server type name
2025-04-18 18:55:10 +08:00
Nathan 2dc22004a1 refactor: remove server type 2025-04-18 15:57:33 +08:00
Nathan 0906febe95 refactor: remove server type 2025-04-18 15:48:17 +08:00
Richard Shiue b12bd8ee85 feat: add medium sized text field (#7737)
* feat: add medium sized text field

* chore: remove height
2025-04-18 15:36:39 +08:00
Richard Shiue e1bfb7095b feat: improve select modal button (#7736) 2025-04-18 15:35:21 +08:00
Richard Shiue 068f93c258 feat: add shadow tokens (#7726) 2025-04-18 15:35:21 +08:00
Richard Shiue d8401e09c9 feat: implement keyboard triggering on buttons and add focus state (#7724)
* feat: implement keyboard triggering on buttons and add focus state

* chore: pass isFocused to builders
2025-04-18 15:35:21 +08:00
Nathan 394ac85c32 refactor: server type name 2025-04-18 15:31:50 +08:00
Nathan.fooo f6e3290aa4 Merge pull request #7777 from AppFlowy-IO/prepare_auto_sync_chat
refactor: save chat and chat message
2025-04-18 15:31:23 +08:00
Nathan 4925e99166 chore: fix compile 2025-04-18 15:02:02 +08:00
Lucas 3bb5075a98 feat: setup/change password in settings page (#7752)
* feat: change password in settings page

* feat: add change password api

* feat: add password service

* feat: add setup password

* feat: refacotor account page

* chor: update i18n

* chore: i18n

* chore: i18n

* feat: add error message under text field

* fix: flutter tests

* chore: remove long password test

* fix: cloud integration test

* fix: cargo clippy

* fix: replace border color
2025-04-18 14:46:46 +08:00
Nathan 59efb7d9e5 chore: fix test 2025-04-18 14:43:01 +08:00
Nathan 165e95c480 chore: fix test 2025-04-18 14:36:47 +08:00
Nathan 31d8653ba6 refactor: save chat and chat message 2025-04-18 13:34:13 +08:00
Nathan.fooo 80df4955e2 Merge pull request #7754 from AppFlowy-IO/local_chat2
feat: support anon local ai chat/writer
2025-04-17 21:48:05 +08:00
Nathan 5436277ada chore: fmt 2025-04-17 21:39:49 +08:00
Nathan ed64719560 chore: clippy 2025-04-17 21:09:24 +08:00
Nathan ac659066c6 chore: return local model 2025-04-17 20:49:24 +08:00
Nathan 3a4d17f054 chore: enable anon ai writer 2025-04-17 16:54:02 +08:00
Nathan 57e4d269eb chore: enable local chat 2025-04-17 16:27:53 +08:00
Nathan c2339c3522 Merge branch 'main' into local_chat2 2025-04-17 15:49:12 +08:00
Nathan 13065ac726 chore: add tests 2025-04-17 15:47:17 +08:00
Nathan af91a72187 chore: select message 2025-04-17 14:07:01 +08:00
Nathan 98b835227e chore: remove unused code 2025-04-17 11:22:14 +08:00
Nathan c633dd0919 chore: remove unused code 2025-04-17 11:11:54 +08:00
Morn fbf928e6e4 chore: update CHANGELOG.md (#7765) 2025-04-17 11:01:30 +08:00
Lucas 8f63667282 feat: upgrade ubuntu version to 22.04 in github action (#7764)
* feat: upgrade ubuntu version to 22.04 in github action

* fix: cargo clippy
2025-04-17 09:48:23 +08:00
Nathan e2896b2911 chore: remove inline view id reference 2025-04-16 22:03:14 +08:00
Nathan 77fbf0f8a3 chore: local ai initialize 2025-04-16 21:26:09 +08:00
Nathan c89f33e2f8 chore: remove unused code 2025-04-16 20:59:25 +08:00
Nathan e5b6393257 Merge branch 'main' into local_chat2 2025-04-16 20:23:55 +08:00
Nathan.fooo 954e844a21 Merge pull request #7761 from AppFlowy-IO/do_not_initialize_collab
fix: 0.8.9 release bugs
2025-04-16 17:06:31 +08:00
Morn 13acc3af86 fix: auto focus on link name textfield after open the link_edit_menu (#7757)
* fix: auto focus on link name textfield after open the link_edit_menu

* chore: update pubspec.yaml
2025-04-16 16:56:37 +08:00
Nathan 079112c9d2 chore: clippy 2025-04-16 16:30:07 +08:00
Nathan 0be8dcc000 chore: remove duplciate 2025-04-16 16:29:28 +08:00
Nathan d7d040b0f9 Merge branch 'main' into do_not_initialize_collab 2025-04-16 16:08:29 +08:00
Nathan f652229718 fix: open relation database row 2025-04-16 16:08:11 +08:00
Lucas f727dde74b fix: lose nested children when accepting AI responses (#7760)
* chore: add gitkeep in assets/font dir

* Revert "feat: improve white label scripts on Windows (#7755)"

This reverts commit a5eb2cdd9a.

* chore: use --verbose

* fix: lose nested children when accept ai response

* chore: lock analyzer version

* Reapply "feat: improve white label scripts on Windows (#7755)"

This reverts commit c73186306e.

* chore: lock analyzer version

* chore: update editor version
2025-04-16 15:59:43 +08:00
Nathan 92179fe61c chore: do not get workspace usage when current user is not owner 2025-04-16 15:16:06 +08:00
Nathan ecfcf3be4d chore: commit pub lock 2025-04-16 14:37:45 +08:00
Nathan be132d867a chore: lock analyzer version 2025-04-16 14:31:10 +08:00
Nathan e6951012f0 chore: do not generate search summary if result is empty 2025-04-16 14:21:16 +08:00
Nathan 3214ec075b chore: fix flaky initialize folder indexer 2025-04-16 14:21:16 +08:00
Nathan be1d2b4b92 chore: delay collab initialization until schema is configured; finalize to ensure collab includes its schema 2025-04-16 14:21:16 +08:00
Lucas a5eb2cdd9a feat: improve white label scripts on Windows (#7755)
* feat: improve white label scripts on Windows

* feat: add font white label script

* chore: integrate font white label script
2025-04-16 10:15:40 +08:00
Morn 9b077969e7 fix: error position for slash search result (#7758) 2025-04-16 10:11:31 +08:00
Nathan.fooo 7160790596 Merge pull request #7748 from AppFlowy-IO/adjust_search_ui
chore: loading for search summary
2025-04-15 22:52:49 +08:00
Nathan 5c3e81e6dc chore: adjust ui 2025-04-15 22:49:34 +08:00
Nathan 846172a709 chore: adjust ui 2025-04-15 22:19:16 +08:00
Nathan f62686fdeb chore: support local chat 2025-04-15 21:07:01 +08:00
Morn c6511cfb55 fix: mobile slash menu position error (#7747)
* fix: mobile slash menu position error

* fix: mobile slash menu sometimes not showing up
2025-04-15 17:43:50 +08:00
Richard Shiue 69b5452af5 fix: undo not working in database row full page (#7749) 2025-04-15 17:17:39 +08:00
Nathan 9291236733 chore: clippy 2025-04-15 16:49:33 +08:00
Nathan 3b3ae7fde9 chore: loading for search summary 2025-04-15 15:59:15 +08:00
Morn d9748d5ef1 fix: block does not expand with grid size (#7745)
* fix: block does not expand with grid size

* fix: replace listenForSizeChanged with SizeChangedLayoutNotifier
2025-04-15 13:50:53 +08:00
Richard Shiue d01909830d fix: ai writer not working in database rows (#7746)
* fix: ai writer not working in database rows

* chore: dart analysis
2025-04-15 11:14:20 +08:00
Nathan.fooo 03ecc3d6c9 Merge pull request #7727 from AppFlowy-IO/search_summary
chore: search summary
2025-04-15 10:37:17 +08:00
Lucas 1630e11c4d chore: update login page icons and i18n (#7742)
* feat: update i18n and icons

* chore: replace appflowy with welcome to appflowy

* fix: protenial delete page error

* fix: flutter analyze
2025-04-15 09:39:33 +08:00
Nathan 99fb6ab743 chore: fix linux watch 2025-04-14 23:07:42 +08:00
Nathan 35bc095760 chore: local and server result 2025-04-14 22:58:37 +08:00
Nathan a44ad63230 chore: display preview 2025-04-14 16:40:54 +08:00
Lucas ddbaf0d530 feat: otp improvement on mobile (#7738) 2025-04-14 14:58:00 +08:00
Nathan 437deaf986 chore: update logs 2025-04-14 14:38:25 +08:00
Morn 54df6b2863 fix: link_preview launch review issues (#7731)
* fix: some link_preview launch review issues

* fix: some UI issues

* chore: pasting a link will not check whether it is an image

* fix: copy link to block not supported well

* fix: mention UI issues

* feat: support get youtube channel info

* chore: update translation

* feat: add shadow in appflowy theme

* chore: remove AFThemeExtensionV2

* fix: some UI issues
2025-04-14 14:14:05 +08:00
Richard Shiue 69b98cb323 fix: open board row as page (#7735) 2025-04-14 10:47:55 +08:00
Nathan 4d172761ce chore: search with ai summary 2025-04-14 10:36:42 +08:00
Lucas 8c4324ee9d fix: otp launch review issues (#7730)
* fix: add error text under text field

* chore: update translation
2025-04-14 10:29:46 +08:00
Lucas 2e295e6891 fix: unable to accept the response from 'mark longer' (#7725)
* fix: unable to accpet 'make it longer'

* fix: markdown text robot test
2025-04-11 14:25:19 +08:00
Richard Shiue 351c891a5a fix: adjust ghost icon hover color (#7723) 2025-04-11 10:21:02 +08:00
Richard Shiue bcb1e8e4f5 chore: adjust replace, insert below, discard selection behavior (#7719) 2025-04-11 09:57:41 +08:00
MornandLucas 4997ac99cf feat: revamp link preivew (#7692)
* feat: revamp link preivew

* feat: add convert to menu for link hover menu

* feat: add mention link

* feat: support convert preview to mention

* feat: add embed link preview

* fix: some test erros

* fix: test errors

* fix: some UI issues

* chore: add test for url

* chore: add test for mention

* chore: add test for bookmark

* chore: add test for embed

* chore: remove unuse import

* fix: some UI issues

* fix: remove text span overlay on mobile

* fix: code lint error

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2025-04-10 14:45:13 +08:00
Lucas e3bbabd63e feat: sign in with passcode (#7685)
* feat: sign in with password or passcode

* feat: add loading dialog

* chore: update translations

* feat: support otp on mobile
2025-04-10 14:06:08 +08:00
Morn 403c558f9b chore: update translation (#7675) 2025-04-10 13:45:22 +08:00
Nathan.fooo 31d2c1d603 Merge pull request #7718 from AppFlowy-IO/remove_path_to_lai
chore: remove local ai related path to lai repo
2025-04-09 21:56:40 +08:00
Nathan a7adeeadb1 chore: remove local ai related path to lai repo 2025-04-09 20:47:03 +08:00
Nathan.fooo f7d7141a59 Merge pull request #7717 from AppFlowy-IO/fix_local_ai_setting
chore: fix local ai setting
2025-04-09 20:43:36 +08:00
Nathan 2371d4691d chore: fix local ai setting 2025-04-09 20:07:06 +08:00
Nathan 9ec0437673 chore: remove enable sync log toggle 2025-04-09 14:15:48 +08:00
Richard Shiue 89525b7f7b chore: bump version (#7716) 2025-04-09 14:11:33 +08:00
Richard Shiue ad227bcf79 chore: toast message when theme folder doesn't have permission (#7715) 2025-04-09 13:45:07 +08:00
Lucas 8b82d532e0 fix: replace the selected text with ai response in the same line (#7708)
* fix: replace the selected text with ai response in the same line

* fix: replace the selected text with ai response in the multiple lines

* fix: integrate the replace function in the ai writer cubit

* fix: unit test and integration test
2025-04-09 13:21:19 +08:00
Morn f4af47728f fix: overwriting a selected text removes more than expected (#7714) 2025-04-09 12:57:58 +08:00
Richard Shiue b3a4a9ba04 fix: use normal underline for AI suggested text style (#7712) 2025-04-09 10:25:29 +08:00
Lucas 13028c6cfe fix: icon and emoji doesn't align in mention page menu (#7673) 2025-04-09 09:39:35 +08:00
Richard Shiue 7d0f6c9deb fix(mobile): edit mention page (#7698)
* fix(mobile): edit mention page

* chore: type check

* chore: replace usages

* chore: use MentionType instead
2025-04-09 09:38:42 +08:00
FakhriAzzouz 8d2901cc58 chore: arabic translations update (#7701)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-04-09 09:38:00 +08:00
Aniket Patil bbc7b6d172 feat(i18n): add initial Marathi (mr-IN) translations (#7667)
* feat(i18n): Add initial Marathi (mr-IN) translations

* fix(i18n): Force add mr-IN.json in assets folder for localization

* fix(i18n): Sync mr-IN.json from assets to resources

* fix(i18n): Final sync of full Marathi translation in assets

* feat(i18n): Force add Marathi translations to ignored directory

* feat(i18n): Complete Marathi translations
2025-04-09 09:37:21 +08:00
Lucas 145d1e5fdb feat: support white label on windows (#7706) 2025-04-09 09:36:53 +08:00
Nathan.fooo eae4e42dcc Merge pull request #7709 from AppFlowy-IO/refactor_path_name
chore: rename data path
2025-04-08 21:19:07 +08:00
Nathan 6886261692 chore: ensure path 2025-04-08 20:49:15 +08:00
Nathan 23f2d85e70 chore: clippy 2025-04-08 20:23:25 +08:00
Nathan d1d598940d chore: clippy 2025-04-08 16:08:28 +08:00
Nathan efb98d28ef chore: rename data path 2025-04-08 15:53:34 +08:00
Richard Shiue 462c822255 fix: translations (#7694) 2025-04-08 13:08:10 +08:00
Richard Shiue 9e30b1816f fix(flutter_desktop): grid bug (#7697)
* fix: field width doesn't persist

* test: add test

* test: fix test

* test: grid width integration test
2025-04-08 11:46:06 +08:00
Nathan.fooo d79929d1c9 Merge pull request #7700 from AppFlowy-IO/sync_log_enable_default
chore: enable sync log by default
2025-04-08 10:31:01 +08:00
Nathan 0286678286 chore: clippy 2025-04-08 10:30:48 +08:00
Nathan 4896d7c1be chore: enable sync log by default 2025-04-07 22:04:32 +08:00
Nathan.fooo b06a2337a0 Merge pull request #7699 from AppFlowy-IO/use_uuid
Chore: Use UUID instead of using String
2025-04-07 21:54:48 +08:00
Nathan 20bcdd1f90 chore: fmt 2025-04-07 21:28:48 +08:00
Nathan 24d57336a9 chore: bump collab id 2025-04-07 21:15:30 +08:00
Nathan 91397c963a chore: clippy 2025-04-07 19:34:26 +08:00
Nathan 995b773c74 chore: replace str with uuid 2025-04-07 19:24:58 +08:00
Richard Shiue c561abd9f8 fix: clear text field upon selection (#7695) 2025-04-07 14:52:44 +08:00
Lucas 10dd0fa438 feat: implement new color tokens design (#7684)
* fix: missing AFThemeExtensionV2 on mobile

* feat: add appflowy_ui package
2025-04-04 14:41:13 +08:00
Nathan 6da7c17a9e chore: upgrade cloud version 2025-04-02 22:37:31 +08:00
Nathan 13d8b1ed37 chore: fix test 2025-04-02 18:03:03 +08:00
Lucas d74b0bf6e1 fix: only log document event when enableDocumentInternalLog is enabled (#7676) 2025-04-02 16:09:52 +08:00
Morn 913924d8d3 fix: emoji menu should only be triggered when “:” has a followed letter (#7672) 2025-04-02 14:15:14 +08:00
Nathan.fooo 7bc9ce4391 Merge pull request #7674 from AppFlowy-IO/custom_compare_func
fix: after applying different ai model, the selection menu doesn't show the new model
2025-04-02 13:59:04 +08:00
Nathan ed5334a7d6 chore: clippy 2025-04-02 13:38:29 +08:00
Nathan 120f22c6fc chore: notify model change after applying local ai model 2025-04-02 13:08:45 +08:00
FakhriAzzouz f8a17dac00 chore: update translations with Fink 🐦 (#7669) 2025-04-02 09:50:50 +08:00
Lucas 7f41feb959 chore: update changelog (#7671) 2025-04-01 21:41:20 +08:00
Nathan.fooo da7f584da7 Merge pull request #7670 from richardshiue/fix/hotfix
fix: translation hotfix
2025-04-01 21:34:06 +08:00
Richard Shiue 031a88f4c4 fix: translation hotfix 2025-04-01 21:26:24 +08:00
Lucas e2ff12415c chore: update translation (#7666) 2025-04-01 17:35:22 +08:00
MornandLucas.Xu edd59cf462 fix: link menu issues (#7661)
* fix: duplicated link menu issue

* fix: support toolbar animation

* chore: update appflowy_editor

* fix: update pubspect.lock

* fix: testing error

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-04-01 14:17:04 +08:00
Nathan 0d0c6fff32 chore: update test 2025-04-01 13:32:28 +08:00
Morn b52f681e3c fix: launch review issues for non-search-bar emoji picker (#7654)
* fix: some launch review issues for emoji picker

* fix: revamp emoji picker which is created by character

* fix: add padding for non-searchbar emoji picker
2025-04-01 09:33:54 +08:00
Lucas.Xu e4187a570c Merge branch 'main' into ai_writer_test 2025-04-01 09:27:16 +08:00
Lucas 5d33d723e9 fix: the checklist item will disappear when reordering (#7659) 2025-04-01 09:25:49 +08:00
Richard Shiue 01dbcf7697 chore: don't try to force update selection 2025-03-31 22:56:15 +08:00
Richard Shiue 2ec68c9269 chore: merge remote-tracking branch 'upstream/main' into this one 2025-03-31 22:54:26 +08:00
Nathan 73e3cbdc18 chore: remove env test 2025-03-31 21:53:56 +08:00
Nathan 5c99572e93 chore: rename test 2025-03-31 21:00:14 +08:00
Nathan 72fe1d7c47 chore: add integration test 2025-03-31 16:19:56 +08:00
Morn 5e1a8b1ec7 fix: toolbar link launch review issues (#7639)
* fix: some toolbar link launch review issues

* fix: support check link format for link menus

* fix: toolbar and link hover menu will not display together

* fix: filter link search result with current document id

* fix: remove error text while link menu is not focus

* fix: some issues

* fix: test errors

* fix: add tooltip for link menu
2025-03-31 15:16:56 +08:00
Richard Shiue 3c99105b23 fix: show cursor at end of selection when generating (#7655) 2025-03-31 15:06:50 +08:00
Richard Shiue 0b298ea426 chore: use emoji instead of Local for copy (#7656) 2025-03-31 15:06:39 +08:00
Lucas 7ed36f8736 feat: support i18n in delete my account operation (#7635) 2025-03-31 13:07:42 +08:00
Richard Shiue 34d2b7f24e chore: improve local ai settings page (#7651)
* chore: improve local ai settings page

* chore: move local to top in select list

* chore: improve copy of missing model

* chore: remove green background and add progress indicator

* chore: change text color
2025-03-31 12:05:11 +08:00
Richard Shiue a2303d35e8 chore: hide formats when using specific ai writer commands (#7648)
* chore: hide formats for specific ai writer features

* chore: use black color for selected model name
2025-03-31 10:24:46 +08:00
Nathan 89647ae683 chore: logs for windows 2025-03-31 00:15:59 +08:00
Nathan.fooo c87d9ab74f Merge pull request #7652 from AppFlowy-IO/show_not_ready
chore: display message when using ai writer with local ai, but local ai  is disabled
2025-03-30 22:16:05 +08:00
Nathan e3a0806eee chore: clippy 2025-03-30 20:53:27 +08:00
Nathan b63c4dfe21 chore: show local ai disable 2025-03-30 20:12:20 +08:00
Nathan 2277d7d234 chore: show local ai disable 2025-03-30 15:15:59 +08:00
Nathan f76ce2be14 chore: show not ready state when using ai writer with local ai 2025-03-30 12:06:08 +08:00
Nathan.fooo 3c74208ab9 Merge pull request #7650 from AppFlowy-IO/display_plugin_version
chore: show plugin version
2025-03-30 12:05:25 +08:00
Nathan 5ae3f42313 chore: fix windows build 2025-03-30 11:28:29 +08:00
Nathan af5c4bfe76 chore: show plugin version 2025-03-30 10:48:26 +08:00
Nathan.fooo 9c25769d8e Merge pull request #7649 from AppFlowy-IO/revert-7643-chore/improve-local-ai-setting-page
Revert "chore: improve local ai settings page"
2025-03-30 09:54:26 +08:00
Nathan.fooo 12a4bf67f6 Revert "feat: improve local ai settings page UI (#7643)"
This reverts commit 3a879b0186.
2025-03-30 09:54:08 +08:00
Nathan 34a858e948 chore: show plugin version 2025-03-30 09:02:06 +08:00
Richard Shiue 3a879b0186 feat: improve local ai settings page UI (#7643)
* chore: improve local ai settings page

* chore: improve local ai settings page

* chore: simplify state and improve ui
2025-03-29 21:56:05 +08:00
Nathan.fooo dbe72b32b2 Merge pull request #7646 from AppFlowy-IO/show_lai_resource
chore: bump lai
2025-03-29 19:58:08 +08:00
Nathan 917aa60c98 chore: bump lai 2025-03-29 15:32:45 +08:00
Nathan.fooo 84e0f5e6ff Merge pull request #7595 from AppFlowy-IO/mcp_protocol
chore: implement MCP client
2025-03-29 09:10:05 +08:00
Nathan 7b89d76cea chore: clippy 2025-03-28 23:31:00 +08:00
Nathan fbf031b06d chore: bump local ai 2025-03-28 23:00:27 +08:00
Nathan 671e855b0e Merge branch 'main' into mcp_protocol 2025-03-28 16:27:19 +08:00
Nathan 8aa32ca3fa chore: disable mcp 2025-03-28 16:27:16 +08:00
Richard Shiue 07c767c4fa fix: ai writer ux issues (#7640)
* chore: fix local ai font weight

* chore: adjust auto response format copy

* fix: escape shortcut

* chore: more action button icon size
2025-03-28 11:23:29 +08:00
Nathan.fooo 4ce0782a05 Merge pull request #7638 from AppFlowy-IO/ai_model_desc
Ai model desc
2025-03-27 22:07:00 +08:00
Nathan 7456c65799 chore: clippy 2025-03-27 20:54:48 +08:00
Nathan 8cf31b8afc chore: bump client api 2025-03-27 20:51:29 +08:00
Nathan ff70595a15 chore: display ai model desc and fix flashing when select model 2025-03-27 20:03:07 +08:00
Nathan f6f19a0a07 chore: display ai model desc on setting page 2025-03-27 19:17:52 +08:00
Richard Shiue b83b964678 chore: add model description UI 2025-03-27 18:01:45 +08:00
Nathan f574b6b9c2 chore: update default name 2025-03-27 17:10:35 +08:00
Nathan 7ee29dcbc5 chore: return model desc 2025-03-27 17:09:01 +08:00
Nathan d348361889 chore: return desc of ai model 2025-03-27 16:31:18 +08:00
Richard Shiue 07a78b4ad7 chore: adjust default model name (#7634) 2025-03-27 14:26:45 +08:00
Morn a26ebbccc1 fix: toolbar launch review issues (#7631)
* fix: keep the turn into menu within six-dot same as toolbar

* fix: change some icon color within toolbar

* fix: improve toolbar

* chore: update editor dependency

* fix: update editor dependency
2025-03-27 14:19:51 +08:00
Nathan ccb020e885 chore: bump lai 2025-03-27 14:19:27 +08:00
Lucas 76cb23e233 feat: add bloc observer (#7633)
* chore: bump version 0.8.8

* feat: add bloc observer

* chore: update comment

* chore: update comment
2025-03-27 14:17:47 +08:00
Morn 4686e13390 feat: add animation for floating toolbar (#7623) 2025-03-27 13:28:35 +08:00
Nathan.fooo 584f762e11 Merge pull request #7617 from richardshiue/chore/improve-model-selection-ui
feat: regenerate message with different model
2025-03-27 12:54:26 +08:00
Richard Shiue 8528811992 chore: remove ai model class 2025-03-27 12:02:58 +08:00
Richard Shiue 9147f64b65 chore: adjust suggestion action button position (#7632) 2025-03-27 11:10:23 +08:00
Richard Shiue f7f2e71ee1 chore: adjust popover shadows (#7630) 2025-03-27 10:05:26 +08:00
Richard Shiue f9e1dcca6c chore: code nit 2025-03-27 00:15:48 +08:00
Richard Shiue e11388491f chore: emit ready state faster 2025-03-27 00:15:48 +08:00
Richard Shiue eb0cff36c9 chore: improve ai model selection ui 2025-03-27 00:15:48 +08:00
Nathan ac8141ab15 Merge branch 'main' into mcp_protocol 2025-03-26 16:18:46 +08:00
Richard Shiue b3b13e550d chore: adjust popover shadows (#7626) 2025-03-26 14:29:11 +08:00
Nathan.fooo ba1767e312 Merge pull request #7628 from AppFlowy-IO/local_ai_global_config
chore: implement local ai config
2025-03-26 14:29:02 +08:00
Nathan 10048dadec Merge branch 'main' into local_ai_global_config 2025-03-26 14:21:12 +08:00
Nathan 815bb11cde chore: implement local ai config 2025-03-26 14:19:57 +08:00
Lucas 7372f5583c chore: bump version 0.8.8 (#7627) 2025-03-26 13:56:24 +08:00
LucasandRichard Shiue 9115e208ac fix: numbered list generated by ai should keep the same index as the input (#7622)
Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
2025-03-26 13:31:32 +08:00
Morn 24bb1b58a0 feat: support use ":" keyword to create emojis (#7582)
* feat: add ability to use : keyword to create emojis(#2797)

* fix: emoji position error

* chore: add integration test

* chore: dismiss emoji picker while starting searching with space
2025-03-26 13:24:16 +08:00
Ametero cfca70ae14 chore: update translations with Fink 🐦 (#7584) 2025-03-26 13:23:25 +08:00
Morn 1db6da7024 fix: undo not working for toggle heading (#7598) 2025-03-26 13:22:47 +08:00
Richard Shiue c212c568e9 chore: bump editor ref (#7621) 2025-03-26 11:00:41 +08:00
Richard Shiue 1d437fb81e chore: adjust ai writer popover content area (#7618) 2025-03-25 23:44:00 +08:00
Richard Shiue 5a0478ad56 fix(mobile): settings page crashes (#7616) 2025-03-25 23:00:28 +08:00
Richard Shiue 039c191d1f fix: ai text insertion (#7615)
* fix: dont scroll to ai writer node if path not found

* chore: rename text robot clear method and add reset

* fix: insert position is off if using ai writer multiple times

* chore: reorganize code

* fix: undo not working after accept
2025-03-25 22:41:35 +08:00
Richard Shiue eb4b015de8 chore: bump editor plugin ref (#7610) 2025-03-25 20:37:15 +08:00
Khor Shu Heng 5269bfbf8e Merge pull request #7609 from ixicut/fix-ukrainian-translation-7603
fix: correct translation inconsistencies
2025-03-25 18:05:30 +08:00
ixicut ed44c20281 fix: correct translation inconsistencies 2025-03-25 11:19:11 +02:00
Nathan.fooo 878323a299 Merge pull request #7606 from AppFlowy-IO/fix_exceptions
chore: fix incorrect local ai state
2025-03-25 13:59:49 +08:00
Nathan 0dc2363962 chore: fix incorrect local ai state 2025-03-25 13:05:18 +08:00
Richard Shiue 72d660f1ac fix: number cell update flashes with old content (#7605) 2025-03-25 11:07:46 +08:00
Nathan.fooo 46532a861f Merge pull request #7602 from AppFlowy-IO/remove_default_model
chore: remove default model name
2025-03-24 22:37:23 +08:00
Nathan 4c39908748 chore: separate model 2025-03-24 22:21:44 +08:00
Nathan 35081fd311 chore: remove default model name 2025-03-24 21:59:42 +08:00
Richard Shiue 7463e4e3eb fix: pass response format in ai writer (#7599) 2025-03-24 16:50:16 +08:00
Nathan 66ce786726 chore: add client 2025-03-24 16:38:37 +08:00
Richard Shiue 682a50da53 chore: replace ai response every time (#7597) 2025-03-24 14:15:20 +08:00
Nathan d372abd5a1 Merge branch 'main' into mcp_protocol 2025-03-24 13:56:56 +08:00
Richard Shiue dfb5a6629f chore: pass ai writer context (#7596)
* chore: pass ai writer context

* chore: maintain selection after starting ai writer

* chore: improve ui of additional comments

* chore: revert podfile.lock changes

* chore: code readability

* chore: revert podfile.lock changes

* fix: accept shouldn't try to unformat
2025-03-24 13:27:31 +08:00
Nathan.fooo bb72f7e70a Merge pull request #7590 from AppFlowy-IO/support_switch_model
chore: support switch ai model in chat/ AI writer
2025-03-24 12:40:43 +08:00
Nathan 37085042f8 chore: clippy 2025-03-24 12:40:29 +08:00
Nathan 2cbcb320fe chore: add timeout 2025-03-24 12:11:12 +08:00
Nathan 949556e2fa chore: remove tauri feature 2025-03-24 12:03:42 +08:00
Nathan 08d1d3602e chore: implement MCP client 2025-03-24 10:44:20 +08:00
Morn 910c45e457 chore: change some mobile slash menu icons (#7579) 2025-03-24 09:55:58 +08:00
Morn 6f031d0c7e feat: revamp toolbar link (#7578)
* feat: revamp toolbar link

* fix: some review issues

* chore: add integration test for toolbar link
2025-03-24 09:55:23 +08:00
Morn 44c9d572c8 fix: using date picker with @ menu with some errors (#7580) 2025-03-24 09:54:45 +08:00
Nathan 05949d2f87 chore: support switch ai model in chat or ai writer 2025-03-23 21:53:05 +08:00
Nathan.fooo ad695e43b9 Merge pull request #7575 from AppFlowy-IO/completion_stream_v2
chore: completion stream v2
2025-03-21 11:47:28 +08:00
Nathan 87015f7133 chore: upgrade lai commit 2025-03-21 11:47:05 +08:00
Richard Shiue deb019aa4a chore: don't allow double register two ai nodes 2025-03-21 11:14:49 +08:00
Richard Shiue c79d014305 chore: revert podfile changes and update error 2025-03-21 10:49:41 +08:00
Richard Shiue 182101023b chore: remove actions in explanation when not explain 2025-03-21 10:37:10 +08:00
Richard Shiue f1b2f51a06 chore: fix test 2025-03-21 10:20:47 +08:00
Nathan a5c0ad5998 chore: fix test 2025-03-21 10:07:27 +08:00
Nathan 6fd250d4d1 chore: update client api 2025-03-21 10:04:51 +08:00
Nathan db2270c8d8 chore: update local ai commit 2025-03-20 15:13:25 +08:00
Richard Shiue 10f19069c6 chore: implement ui 2025-03-20 13:30:42 +08:00
Richard Shiue 13a7ea07a8 chore: merge remote-tracking branch 'upstream/main' into this one 2025-03-20 13:29:03 +08:00
Richard Shiue aacd795ae0 chore: ai writer more actions (#7576)
* feat: add more button to ai writer input box

* chore: adjust button padding

* chore: adjust icon color
2025-03-20 12:33:51 +08:00
Richard Shiue 566e7b2f40 feat: allow user scroll during generation (#7559)
* chore: add keep alive to ai writer block component

* chore: allow user scrolling during ai writer generation

* chore: pull ai writer cubit upwards

* test: fix unit tests

* chore: clear selection
2025-03-20 11:50:25 +08:00
Nathan f413b9e070 chore: callback 2025-03-20 11:44:02 +08:00
Nathan 6e4206a8e2 chore: completion stream v2 2025-03-20 11:41:49 +08:00
Nathan 954aa48f52 chore: bump lai commit 2025-03-19 14:17:17 +08:00
Richard Shiue 461ac91b32 fix: continue writing empty text case (#7574) 2025-03-19 14:13:20 +08:00
Nathan.fooo 8a9cc278ec Merge pull request #7573 from AppFlowy-IO/rename_plugin
chore: rename the local ai plugin
2025-03-19 11:29:23 +08:00
Morn 9db87944f2 fix: toolbar tooltip message is incorrect on Windows (#7572)
* fix: some toolbar text display error

* chore: change string id to enum string id
2025-03-19 11:23:07 +08:00
Nathan 9230981e54 chore: remove test 2025-03-19 10:59:11 +08:00
Nathan 72b13dd941 chore: adjust UI 2025-03-19 10:48:30 +08:00
FakhriAzzouz e6b0c8ff05 chore: update Arabic translations (#7571)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-03-19 10:45:57 +08:00
Nathan f0d967f0e4 chore: rename the local ai plugin 2025-03-19 10:23:38 +08:00
Morn 85b9aab015 chore: update CHANGELOG.md (#7569) 2025-03-18 18:46:07 +08:00
Morn 69571f668c fix: some launch review issues (#7566)
* chore: adjust some toolbar text

* chore: change the icons in turn-into menu

* chore: change the icon in color menu

* fix: keep selection after doing some changes from toolbar

* fix: color menu displaying error

* fix: wrong filter logic in toolbar suggestion menu

* fix: some launch review issues

* fix: test errors
2025-03-18 18:45:31 +08:00
Lucas a89dd87c16 fix: optimize cover title position offset calculation (#7568)
* fix: optimize cover title position offset calculation

* feat: ingore shift+enter in callout/quote and fallback to system behavior
2025-03-18 17:53:21 +08:00
Richard Shiue 22b03eee29 chore: implement ai writer history (#7523)
* chore: implement ai writer history

* chore: pass hitosyr
2025-03-18 17:14:20 +08:00
Nathan.fooo e3ea3fcdfa Merge pull request #7570 from AppFlowy-IO/update_local_ai_translation
chore: update local ai translation
2025-03-18 16:43:39 +08:00
Nathan ccfbde9a92 chore: update local ai translation 2025-03-18 16:42:29 +08:00
Nathan.fooo 7358860bfc Merge pull request #7567 from AppFlowy-IO/fix_release_crash_2
chore: update commit
2025-03-18 16:07:25 +08:00
Nathan 17b355197c chore: update commit 2025-03-18 15:54:16 +08:00
Nathan.fooo aa7e50cc6c Merge pull request #7565 from AppFlowy-IO/update_translation
chore: update translation
2025-03-18 13:00:33 +08:00
Nathan 69ce105806 chore: update translation 2025-03-18 12:54:45 +08:00
Lucas cafdfcca51 feat: add download button in file block (#7562) 2025-03-18 11:31:18 +08:00
a8c5c9c34e chore(i18n): add and fine-tune ko-KR translations (#7548)
* feat(translations): reset hard Korean translation

Co-authored-by: NavyStack <navystack@askfront.com>
Co-authored-by: FVOCI <150913557+fvoci@users.noreply.github.com>

* i18n(ko-KR): Add new translations and fine-tune existing ones

Co-authored-by: NavyStack <navystack@askfront.com>
Co-authored-by: FVOCI <150913557+fvoci@users.noreply.github.com>

---------

Co-authored-by: fvoci <karl@hwan.dev>
Co-authored-by: FVOCI <150913557+fvoci@users.noreply.github.com>
2025-03-18 10:30:51 +08:00
Morn 6dd83675fc fix: toolbar launch review issues (#7532)
* fix: some launch review issues

* fix: some launch review issues

* fix: color picker position error

* fix: redesign the dropdown arrow and padding

* feat: implement toolbar button state

* fix: keep custom color not changed

* feat: revamp color icon in toolbar

* fix: correct toolbar position & add animation for toolbar

* fix: ajust toolbar animation parameters

* chore: adjust some UI values

* fix: keep selection after turn into

* fix: hover color on toolbar is wrong in dark mode

* fix: toolbar icon color in dark mode
2025-03-17 18:03:23 +08:00
Lucas b65fad6214 chore: update template link url (#7557) 2025-03-17 16:57:02 +08:00
Lucas 884046ba3f fix: first added cover might be invisible (#7555) 2025-03-17 15:41:54 +08:00
Lucas 6d327adb83 fix: quote block flashes in ai chat page when generating answer (#7553) 2025-03-17 13:03:47 +08:00
Richard Shiue eddb623fba fix: message hover action flashing (#7552) 2025-03-17 11:02:42 +08:00
Lucas 2ea8e831cd fix: the workflow of switching to Local in app (#7540)
* fix: unable to redo undo when the selection is null

* fix: the workflow of switching to Local in app

* fix: text color doesn't work in table cell

* fix: test
2025-03-17 09:36:46 +08:00
Nathan.fooo 270c981051 Merge pull request #7549 from AppFlowy-IO/fix_internal_stream
chore: bump client api
2025-03-16 20:07:48 +08:00
Nathan 7971566159 chore: bump client api 2025-03-16 19:45:51 +08:00
Nathan.fooo 9fbf2d5ef6 Merge pull request #7547 from AppFlowy-IO/related_question_height
chore: set releated question height
2025-03-16 13:34:34 +08:00
Nathan 21bf2968a9 chore: set releated question height 2025-03-16 12:14:11 +08:00
Nathan.fooo 117950c922 Merge pull request #7546 from AppFlowy-IO/local_embed
chore: local ai embed file
2025-03-16 12:12:45 +08:00
Nathan b4c56f7998 Merge branch 'main' into local_embed 2025-03-16 10:23:21 +08:00
Nathan af0c802486 chore: local ai embed file 2025-03-16 10:22:58 +08:00
Lucas 33d518f383 chore: update unsplash client (#7543) 2025-03-14 21:52:44 +08:00
Nathan.fooo 1f9fe89f87 Merge pull request #7542 from AppFlowy-IO/replace_guide_url
chore: replace guide url
2025-03-14 21:52:06 +08:00
Nathan 5fef4f1d49 chore: replace guide url 2025-03-14 21:51:21 +08:00
Nathan.fooo d6446872ee Merge pull request #7541 from AppFlowy-IO/disable_chat_with_file
chore: disable chat with file
2025-03-14 20:55:34 +08:00
Nathan 5b5feb2515 chore: disable chat with file 2025-03-14 20:53:14 +08:00
Nathan.fooo b1bca1b55b Merge pull request #7538 from AppFlowy-IO/update_local_ai_log
Update local ai log
2025-03-14 18:32:36 +08:00
Nathan 4e1a70c7ac chore: update local ai crate 2025-03-14 17:15:12 +08:00
Nathan 54f25e4b91 Merge branch 'main' into update_local_ai_log 2025-03-14 14:29:35 +08:00
Richard Shiue aa176f2c12 fix(mobile): image formats not shown (#7537) 2025-03-14 14:14:40 +08:00
Nathan 58895620c1 chore: update local ai logs 2025-03-14 13:23:41 +08:00
Nathan e10aade895 chore: update local ai client 2025-03-14 11:36:30 +08:00
Richard Shiue 1fdd7c343b chore: use tooltip instead of multi line for related questions (#7533) 2025-03-14 11:31:05 +08:00
Richard Shiue e36b08cd14 chore: add tooltip for disabled select messages (#7536) 2025-03-14 11:10:52 +08:00
Richard Shiue 6ac9ad1cac fix: continue writing edge case (#7535) 2025-03-14 11:10:40 +08:00
Richard Shiue 36bf90e81b fix: stop generating in ai chat and writer (#7534)
* fix: stop generating shortcuts not working edge cases

* chore: add tooltip
2025-03-14 11:10:29 +08:00
Nathan 22b9acf386 chore: send local ai state 2025-03-13 19:51:42 +08:00
Nathan.fooo e4e75acdac Merge pull request #7527 from AppFlowy-IO/fix_windows_terminal
Fix windows terminal
2025-03-13 19:32:07 +08:00
Nathan 7996736592 chore: enable linux local ai 2025-03-13 17:27:55 +08:00
Nathan 3ad8f624cf chore: enable linux local ai 2025-03-13 17:03:49 +08:00
Nathan 0657aeb07d chore: set local ai default value 2025-03-13 17:02:32 +08:00
Nathan 723971e423 chore: fix editable 2025-03-13 16:44:41 +08:00
Nathan 86b67a1b65 chore: fix flashing window 2025-03-13 15:49:44 +08:00
Nathan d94b4daa70 chore: fix flashing window 2025-03-13 15:29:58 +08:00
Lucas.Xu ad62e85b3a Merge branch 'main' into fix_windows_terminal 2025-03-13 15:09:26 +08:00
Lucas 133eec8163 chore: update dsb_pub.pem on Windows (#7528) 2025-03-13 14:30:09 +08:00
Nathan 81bac5950c chore: try to fix windows flashing window issue 2025-03-13 14:01:41 +08:00
Lucas 651046ab68 fix: unable to paste iframe in editor (#7525) 2025-03-13 13:58:24 +08:00
Morn 9bd13ac29e fix: the slash menu position sometimes is wrong (#7492) 2025-03-13 13:58:06 +08:00
Morn c7d3d612ae fix: emoji picker position error (#7497)
* fix: click an emoji should close the menu when using /emoji to insert an emoji into a doc

* fix: emoji picker position error

* fix: document emoji icon is clipped on Android
2025-03-13 13:57:58 +08:00
Morn 69dd2ab20f feat: revamp toolbar UI (#7506)
* feat: revamp toolbar UI

* fix: integration test issues

* feat: add suggestions for toolbar

* feat: support dark mode

* chore: update editor dependency

* feat: add testing for suggestions

* chore: update editor dependency
2025-03-13 13:51:03 +08:00
Nathan f0b8b00461 chore: force restart plugin 2025-03-13 13:45:01 +08:00
Nathan.fooo 2b8aaf1d46 Merge pull request #7522 from AppFlowy-IO/response_format_local_ai
chore: support response format
2025-03-13 13:08:52 +08:00
Nathan ee69283a23 chore: support response format 2025-03-13 10:53:20 +08:00
Lucas caaf5f7986 chore: remove deprecated pem files (#7521) 2025-03-13 10:27:11 +08:00
Richard Shiue 392964ffd2 chore: disable add messages to page when chat is empty (#7518) 2025-03-13 09:50:46 +08:00
Lucas 1f76412790 fix: nested list issue in quote/callout block (#7513) 2025-03-13 09:26:56 +08:00
Richard Shiue 555254e8fe chore: add ai writer keyboard shortcuts (#7516) 2025-03-12 21:46:06 +08:00
Nathan.fooo 3aa55f83b1 chore: Merge pull request #7515 from AppFlowy-IO/local_ai_opti
chore: disable input when local ai is initializing
2025-03-12 21:20:48 +08:00
Nathan d15a8a88a6 chore: disable input when local ai is initializing 2025-03-12 20:29:03 +08:00
Nathan 1f7ab9d22d chore: fix ios compile 2025-03-12 15:08:37 +08:00
Morn 44945b2912 fix: some slash menu issues (#7501)
* fix: slash menu unexpectedly overflows the screen

* fix: the style of no result doesn’t match the design

* fix: unexpected flashing effect on 2nd level menu item

* fix: can not back to last level through backspace
2025-03-12 14:10:41 +08:00
Lucas 8d50caa86e fix: remove layout builder in quote block (#7508)
* fix: remove layout builder in quote block

* fix: quote block selection color

* fix: quote block and callout block background color issue

* fix: background color in callout block

* fix: quote block layout on mobile
2025-03-12 13:54:24 +08:00
Richard Shiue b59eba76a6 fix: hide continue writing when document is empty (#7498)
* fix: hide continue writing when document is empty

* chore: code clean up and add documentation
2025-03-12 13:54:00 +08:00
Lucas 070cde9ecb chore: bump version 0.8.7 (#7512) 2025-03-12 13:34:59 +08:00
FakhriAzzouz 1e81e4c68f chore: update arabic translation
Arabic Translation Updates
2025-03-12 13:32:24 +08:00
Nathan.fooo 402ca7d765 Merge pull request #7511 from AppFlowy-IO/fix_ios_build
chore: fix ios build
2025-03-12 11:50:13 +08:00
Khor Shu Heng d0ca7f311c Merge pull request #7509 from khorshuheng/fix-recursion-trash-view
fix: prevent segfault due to infinite recursion in trash view
2025-03-12 11:42:40 +08:00
Nathan e553627ee5 chore: fix ios build 2025-03-12 11:16:51 +08:00
khorshuheng cbdac71025 fix: prevent segfault due to infinite recursion in trash view 2025-03-12 10:21:21 +08:00
Nathan.fooo 6f35ae9857 Merge pull request #7504 from richardshiue/chore/ollama-hide-formats
chore: hide predefined format section when using local ai
2025-03-12 09:57:44 +08:00
Nathan 20d64cc7ae chore: lint 2025-03-12 00:09:20 +08:00
Nathan 654e18aacf chore: remove local ai 2025-03-11 23:19:20 +08:00
Richard Shiue 75dd5c1d93 chore: regenerate 2025-03-11 22:18:44 +08:00
Richard Shiue f8f9c3404a chore: show text options still 2025-03-11 22:05:30 +08:00
Richard Shiue 01e5817b24 chore: code cleanup 2025-03-11 17:49:51 +08:00
Richard Shiue 96608bd005 chore: hide predefined format section when using local ai 2025-03-11 17:06:55 +08:00
Nathan.fooo 57a5b38509 Merge pull request #7488 from AppFlowy-IO/ollama
feat: support Ollama
2025-03-11 13:53:35 +08:00
Nathan 83c53188e3 chore: clippy 2025-03-11 13:22:59 +08:00
Nathan 6ba7f93f69 chore: find plugin load 2025-03-11 13:14:47 +08:00
Nathan 702a486cce chore: find windows exe 2025-03-11 12:55:19 +08:00
Richard Shiue eb0ed1ad86 fix: don't allow selection of text in related questions or loading (#7500) 2025-03-11 11:38:20 +08:00
Lucas e264b3a5b8 Merge pull request #7490 from richardshiue/fix/callout-icon-in-markdown
fix: don't include icon while exporting callout to md
2025-03-11 10:38:46 +08:00
Richard Shiue 667d15c627 fix: ai writer gestures (#7499) 2025-03-11 10:31:08 +08:00
Nathan bd06e1d559 chore: clippy 2025-03-11 09:32:20 +08:00
Lucas 459aca5291 Merge pull request #7496 from LucasXu0/ai_writer_position_improvement
feat: ensure the ai writer block visible when generating result
2025-03-11 09:31:24 +08:00
Nathan e7cd90b6ab chore: update commit 2025-03-11 09:14:11 +08:00
Nathan 940db70447 chore: fix build 2025-03-11 00:27:55 +08:00
Nathan 59139ff323 chore: catch panic 2025-03-10 23:40:28 +08:00
Nathan 22fed1bfbc chore: load from env command 2025-03-10 22:48:09 +08:00
Lucas.Xu 5e593bd36e chore: update appflowy editor version 2025-03-10 21:08:28 +08:00
Lucas.Xu ba1dfc6de4 feat: ensure the ai writer block visible when generating result 2025-03-10 19:18:06 +08:00
Lucas c81f87dcdc feat: make the columns block same width width the editor (#7493)
* feat: make the columns block same width width the editor

* chore: turn off column debug mode

* feat: add block selection container in outline block

* feat: use ratio instead of width in simple columns

* fix: document rules

* fix: turn off debug mode

* fix: update the existing columns block data
2025-03-10 18:13:15 +08:00
Nathan a8b55ca3f0 chore: update prompt 2025-03-10 15:56:09 +08:00
Richard Shiue 0cefaf633c fix: fix test 2025-03-10 14:56:01 +08:00
Richard Shiue ba4aebd005 chore: merge remote-tracking branch 'main' into this one 2025-03-10 13:36:08 +08:00
Lucas 7b32a92290 fix: callout block build error (#7491) 2025-03-10 13:04:29 +08:00
Richard Shiue 41b99209f1 fix: retain if is emoji 2025-03-10 12:10:59 +08:00
Morn c1612fe298 feat: add custom icons for callout (#7449) 2025-03-10 11:46:55 +08:00
Lucas e69a09d332 feat: support nested list in callout block and quote block (#7479)
* feat: support nested list in callout block

* chore: update pubspec.yml

* feat: add new quote block

* feat: support nested list in quote block

* feat: refacotr quote block

* feat: optimize quote block align

* feat: support nested list in quote block

* fix: icon and drag menu overlap

* chore: update appflowy editor version

* feat: support trailing action builder for plugin blocks

* chore: update appflowy editor version
2025-03-10 11:46:17 +08:00
Richard Shiue 4e0d9fdb0b fix: don't include icon while exporting callout to md 2025-03-10 11:08:22 +08:00
Nathan 8b2e769fca chore: ai setting ui 2025-03-10 10:24:55 +08:00
Nathan d29a90a472 chore: init ai plugin on separate thread 2025-03-10 08:54:32 +08:00
Nathan 2e4beb0652 chore: enable windows 2025-03-10 00:35:52 +08:00
Nathan addb041816 chore: update exe path 2025-03-10 00:25:19 +08:00
Nathan 4ff71b5dce chore: implement ollama 2025-03-09 23:32:42 +08:00
Richard Shiue a0ae62d6f5 fix: consider simple table in exclude table types (#7478) 2025-03-07 12:32:27 +08:00
Lucas ea18aa7551 chore: bump collab version 45239d2 (#7477) 2025-03-07 11:17:56 +08:00
Morn 68e7069e92 chore: bump version 0.8.6 & update changelog (#7473)
* chore: bump version 0.8.6

* chore: update changelog
2025-03-06 22:03:30 +08:00
Morn 556d929b67 fix: error caused by ScrollablePositionedList(#7460) (#7469)
* fix: error caused by ScrollablePositionedList

* chore: update appflowy_editor version
2025-03-06 18:45:27 +08:00
Lucas 7f3469a0f2 feat: use undo to revert the autotypograph (#7472) 2025-03-06 18:22:10 +08:00
Lucas a062c4aadb fix: bulleted list icon does not center in the columns (#7471) 2025-03-06 16:59:58 +08:00
Morn 3d3f81ad52 fix: complete the missing icons in the database (#7464)
* fix: complete the missing icons in the database

* fix: the toggle is slower than the actual change taken into effect
2025-03-06 16:04:54 +08:00
Lucas fc0fb0b3d3 fix: update locked page button background color (#7470) 2025-03-06 16:02:56 +08:00
Richard Shiue 884586f0af fix: ai writer issues (#7467)
* fix: disable ai writer in table

* fix: enable header row by default when converting from md

* chore: add title when continue writing

* chore: rewrite using predefined format

* fix: mouse & keyboard event still propagate

* chore: bump editor ref
2025-03-06 15:09:33 +08:00
Morn f8c18afbcf fix: improve the experience of using icon color picker (#7468) 2025-03-06 12:39:18 +08:00
Lucas 8046177d84 fix: simple columns issues (#7466)
* Revert "feat: use flutter_distrubutor to build linux and macos packages (#7392)"

This reverts commit 6dc45c9830.

* fix: linux link issue

* fix: outline doesn't work well in columns

* fix: cannot drag a block under a table that’s in the second column
2025-03-06 12:33:53 +08:00
Morn 8d8fc91391 fix: title position working incorrectly with document width setting (#7465) 2025-03-06 12:33:12 +08:00
Richard Shiue 796fda159e chore: bump client api (#7463) 2025-03-06 09:59:53 +08:00
Nathan.fooo 4b2389dafd chore: bump client api (#7455) 2025-03-05 10:23:28 +08:00
Richard ShiueandLucas.Xu 2dd7e5937f fix: incorrect popover position (#7452)
* fix: incorrect popover position

* fix: tests

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-03-04 20:20:26 +08:00
Lucas e9371029f3 chore: update changelog (#7451) 2025-03-04 17:17:57 +08:00
Richard Shiue bbec60ff02 fix(flutter_desktop): page name overflow in search (#7450) 2025-03-04 17:17:42 +08:00
Lucas 3bf4f080c5 feat: auto calculate the column width when resizing (#7448)
* feat: calculate the column width auto

* fix: ai writer table issue
2025-03-04 16:34:26 +08:00
Richard Shiue 637c043f5b fix: ai writer launch review 0.8.5 (#7445)
* fix: improve writing not working

* fix: show insert below and discard buttons even in overflow

* fix: incorrect predefined format initialization

* fix: generate image

* chore: multi-line related questions

* fix: add to undo history

* fix: disable keyboard service when using ai writer

* fix: disable drag nodes

* fix: strikethrough text after accepting

* fix: undo
2025-03-04 16:34:07 +08:00
MornandLucas 9eed993421 feat: refactor databse styles (#7405)
* feat: refactor databse styles

* feat: support compact mode for databse

* feat: support dynamic height for board

* fix: add reference icon for database view in document

* feat: support data sync for database node in document

* fix: add hover effect in compact mode switcher

* fix: title of document not align correctly with a large screen

* fix: some launch review issues

* fix: auto hide the Hidden Groups unless the user clicks it to show

* fix: testing error

* chore: update board version

* chore: update database menu buttons

* fix: some launch review issues

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2025-03-04 11:29:38 +08:00
Lucas aff720c1f1 fix: hide the improve writing button (#7443)
* fix: hide the improve writing button

* chore: update appflowy_editor version

* fix: simple column width

* Revert "fix: hide the improve writing button"

This reverts commit 815a28971c.
2025-03-04 11:29:24 +08:00
Lucas 655de30df5 fix: unable to delete the callout block when it's in the first line (#7442) 2025-03-03 16:40:41 +08:00
Richard Shiue fe6217bd82 feat: ai writer block (#7406)
* feat: ai writer block

* test: fix integration tests

* chore: add continue writing to slash menu

* chore: focus issues during insertion

* fix: explain button position

* fix: gesture detection

* fix: insert below

* fix: undo

* chore: improve writing toolbar item

* chore: pass predefined format when using quick commands

* fix: continue writing in an empty document or at the beginning of a document

* fix: don't allow selecting text not in content

* fix: related question not following predefined format
2025-03-03 13:35:51 +08:00
Morn eacd7b2503 fix: error display when showing SnackBar with dialog (#7440) 2025-03-03 13:15:50 +08:00
Lucas 2e17fb9dd3 fix: can't open the relation field in the linked database (#7441) 2025-03-03 13:14:29 +08:00
Morn 249543d64f fix: using [[ to create subpage with error text (#7434) 2025-03-03 11:22:40 +08:00
Lucas 8ebd490260 feat: support scrollable columns block (#7429)
* feat: support scrollable columns block

* fix: simple columns block issues on mobile

* feat: hide drag menu when resizing the columns block
2025-03-03 11:22:13 +08:00
Morn c0dfec8b34 fix: potential issues with displaying CircleAvatar (#7432) 2025-03-03 11:21:44 +08:00
Lucas 56a023c98a fix: the locked hint is not visible when there's a cover (#7439) 2025-03-03 11:20:58 +08:00
Lucas adcac881a7 fix: 0.8.5 launch review issues (#7430)
* chore: replace two columns with 2 columns

* fix: hide drag menu when the doc is locked

* feat: add placeholder when editing the paragraph

* fix: ingore tab shortcut in document title

* feat: forward the video block to link preview block
2025-03-03 09:57:36 +08:00
Lucas f73342d902 fix: auto updater should not block the launch process (#7427) 2025-02-28 15:18:21 +08:00
FakhriAzzouz 45b0233c21 chore: update Arabic translations (#7361)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-02-27 13:14:48 +08:00
Lucas db349519cf chore: bump version 0.8.5 (#7421) 2025-02-27 13:13:49 +08:00
Lucas c760a1b1fe feat: support columns block in editor on desktop (#7402)
* feat: support columns block in editor

* feat: upgrade simple columns block

* fix: build error

* feat: add column width resizer

* fix: drag visual border

* fix: drag button position issue

* feat: add rule to check if the column is empty

* fix: flutter analyze

* feat: add document rules to delete the columns if its children are empty

* feat: support adding image in columns block

* feat: integrate block actions in columns block

* feat: support dragging to create a columns block

* feat: drag a block into an existing columns block

* feat: add delete columns and delete column rules

* feat: dragging the block to the left side of another block to create a columns block

* feat: support 2-4 columns block in slash menu

* chore: disable debug flag in columns block

* chore: update pubspec.yaml

* chore: update translations and icons

* fix: cloud integration test

* fix: integration test
2025-02-27 13:08:49 +08:00
MornandLucas c5fa9039b4 fix: add shortcut to create Inline Math Equation (#7401)
* fix: add shortcut to create Math Equation(#7331)

* chore: update code

Co-authored-by: Lucas <lucas.xu@appflowy.io>

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2025-02-26 10:06:28 +08:00
Richard Shiue 7eaafc52ce fix: adjust other user message alignment (#7414) 2025-02-25 10:35:53 +08:00
Richard Shiue 63239893ab chore(flutter): move other user message to end (#7413) 2025-02-24 15:14:26 +08:00
Annie e9a1a1ced0 chore: Update README.md (#7411)
change appflowy.io to appflowy.com wherever possible
2025-02-23 13:46:13 +08:00
LucasandMorn 6dc45c9830 feat: use flutter_distrubutor to build linux and macos packages (#7392)
* feat: use flutter_distrubutor to build linux packages

* feat: verify deb on Linux

* chore: update rpm deps

* chore: update codesign files

* chore: update rpm make_config.yaml

* chore: update release.yml

* chore: update release.yml

* chore: update feed url

* chore: rename AppFlowy to appflowy

* chore: update CHANGELOG.md (#7397)

* chore: create release path if not exist

* feat: support appimage

* Revert "feat: support appimage"

This reverts commit cb7dcf725c.

* fix: cp deb/rpm error

* feat: support appimage

* chore: add linux build script

* feat: add macos build script

* feat: update linux scripts

* chore: update linux scripts

* chore: update relesae script

* chore: update macos build scripts

* chore: rename macOS package name

* chore: add keychain in release.yaml

* chore: update macos build steps in release.yaml

* chore: update macos script desc

* chore: remove sudo

* feat: support tar.xz package type

* feat: support tar.xz package type

* chore: add fuse

---------

Co-authored-by: Morn <agedchen@gmail.com>
2025-02-21 17:39:13 +08:00
Morn 58f7659d55 fix: avatar displays error (#7403) 2025-02-21 14:28:24 +08:00
Morn fd12d3a0b0 chore: update CHANGELOG.md (#7397) 2025-02-18 16:48:33 +08:00
Lucas f25821e84d chore: update auto updater feed url (#7396) 2025-02-18 16:14:13 +08:00
Morn c0aa0e0509 fix: some launch review issues (#7379)
* fix: some launch review issues

* fix: some launch review issues

* fix: some launch review issues

* feat: add change button for icon uploader
2025-02-17 16:08:51 +08:00
Lucas 55fbb7522b feat: switch ai mode on mobile (#7391)
* fix: the default page name should be empty when creating

* feat: switch ai model on mobile
2025-02-17 16:08:38 +08:00
Li, John Gen 15b4d496fd chore: update translations (#7387)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-02-17 15:23:11 +08:00
Lucas ad54ad0614 fix: the default page name should be empty when creating (#7389) 2025-02-17 15:13:25 +08:00
Lucas b18bbd0e82 fix: auto updater issue on Windows (#7385) 2025-02-17 09:56:30 +08:00
Lucas e028e45e93 fix: lock page issues (#7380)
* fix: unable to click the swith to lock/unlock page

* fix: add divider above delete button on mobile

* fix: enable lock/unlock page by tapping the lock icon

* chore: update translations

* fix: hide cursor when the page is locked

* fix: the inline databaes still can be edited if the document is locked

* fix: disable auto update checker

* chore: change my account to account & app
2025-02-14 17:02:25 +08:00
Lucas 0e7ac85f90 chore: add ai images label (#7376) 2025-02-13 14:56:16 +08:00
Lucas 8bb2541862 chore: upgrade version (#7374) 2025-02-13 13:57:37 +08:00
Lucas 133e61befd fix: clear background color of header row & header column (#7373) 2025-02-13 13:21:02 +08:00
Morn 9e98680861 feat: support slash menu on mobile (#7368)
* feat: support slash menu on mobile

* feat: support at menu on mobile

* feat: support plus menu on mobile
2025-02-13 12:45:56 +08:00
Khor Shu Heng b75fd673cd chore: update collab version (#7372) 2025-02-13 12:45:45 +08:00
Lucas 4a7e20b3a5 fix: save image should not copy the image (#7370)
* fix: save image should not copy the image

* fix: unable to scroll the table in AI Chat on mobile
2025-02-12 17:35:59 +08:00
MornandLucas.Xu bbe746c564 feat: support upload svg as icon (#7270)
* feat: support upload svg as icon

* feat: support upload icon by pasting a link

* feat: delete remote images when remove custon icons

* chore: add testing for pasting image link as custon icon

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-02-12 15:08:50 +08:00
Reagan lee 189faa4def chore: update translations (#7351)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-02-12 15:08:01 +08:00
Lucas c1a8d89938 feat: lock page on mobile (#7366)
* feat: support lock button in view more actions

* feat: add lock page on mobile

* feat: disable actions in locked page

* feat: disable more actions in locked page

* feat: support locked grid on mobile

* feat: support locked board/calendar on mobile

* fix: exclude lock page button from AI Chat
2025-02-12 15:07:21 +08:00
Lucas 71ce9affbe feat: lock page (#7353)
* feat: lock page

* feat: add pageLockStatus bloc

* feat: add lock status and unlock status in title bar

* feat: add loading lock status

* feat: disable moveTo, delete, rename, updateIcon operations if the page is locked

* fix: lock toast issue

* feat: support locked database

* feat: support locked grid

* feat: support locked title

* feat: support locked board

* feat: support locked calendar
2025-02-12 09:49:36 +08:00
Morn 552dba5abe fix: support exporting more content to markdown (#7333)
* fix: support exporting to markdown with multiple images

* fix: support exporting to markdown with database

* fix: support exporting to markdown with date or reminder

* fix: support exporting to markdown with subpage and page reference

* chore: add some testing for markdown parser

* chore: add testing for exporting markdown with databse as csv
2025-02-11 21:46:02 +08:00
Richard Shiue 04e3246976 chore: rename predefined format enum variant (#7359) 2025-02-11 16:40:39 +08:00
Morn 5d73c3d194 fix: gallery not rendering in row page (#7349) 2025-02-11 14:03:49 +08:00
Richard Shiue 12d9a98831 chore: disable impeller on android (#7355) 2025-02-11 14:03:29 +08:00
Lucas 8f646a2843 feat: integrate version checker for Linux (#7346)
* feat: integrate auto_updater in macOS

* chore: update translations

* chore: bump auto_updater version

* feat: exclude linux platform in auto update task

* feat: support auto_updater on Linux

* chore: combine version checker and auto updater into same class
2025-02-10 15:07:53 +08:00
Lucas f53e9d6549 feat: integrate auto_updater for macOS (#7328)
* feat: integrate auto_updater in macOS

* chore: update translations

* chore: bump auto_updater version

* feat: exclude linux platform in auto update task

* chore: disable auto updater

* fix: integration tests

* fix: integration tests
2025-02-10 09:20:24 +08:00
Richard Shiue fc9c152553 fix(flutter_desktop): selection in AI chat going missing while scrolling (#7281) 2025-02-07 18:44:55 +08:00
Lucas 00cdee831d chore: upgrade to Flutter 3.27.4 (#7230) 2025-02-07 18:17:46 +08:00
Morn 17ae05a623 fix: pasting a link on iOS results in incorrect behavior (#7326) 2025-02-07 14:49:50 +08:00
hasanbeder 8b1a03713b feat(i18n): Add Turkish (tr-TR) language translation (#7329)
- Complete Turkish language translation for AppFlowy
- Covers all UI elements and user-facing strings
- Improves localization support for Turkish users
2025-02-07 09:58:15 +08:00
Richard Shiue e4b57033b4 fix: improve calendar event button icon color and add confirm dialog (#7330)
* fix: improve calendar event button icon color and add confirm dialog

* test: update test
2025-02-06 22:46:22 +08:00
Richard ShiueandNathan 62a6fb8913 chore: optional response format (#7204)
* chore: optional response format

* chore: bump client api

* chore: code cleanup

* chore: bump client api

---------

Co-authored-by: Nathan <nathan@appflowy.io>
2025-02-06 18:10:23 +08:00
FakhriAzzouz 0d89e22ed2 chore(i18n): update ar-SA translations (#7312)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-02-06 13:54:16 +08:00
ArtemisOne ff2aae213c chore(i18n): update es-VE and ga-IE translations (#7320)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-02-06 13:53:24 +08:00
Richard Shiue 43e64d8219 test: fix image integration test (#7323) 2025-02-05 19:45:21 +08:00
Richard Shiue 6823fe5d24 fix: row document images (#7322)
* fix: row document images

* fix: calendar

* chore: fallback to documentbloc
2025-02-05 16:40:03 +08:00
Lucas f683085618 fix: unable to upload file on Android devices (#7314) 2025-02-04 20:57:06 +08:00
Nathan.fooo 71a22dc466 chore: fix ai page user profile refresh (#7317) 2025-02-04 20:29:56 +08:00
Lucas eb508a3ec9 fix: editor stuck on image loading loop when uploading image in row document (#7313)
* fix: editor stuck on image loading loop when uploading image in row document

* test: editor stuck on image loading loop when uploading image in row document
2025-02-04 14:05:57 +08:00
Nathan.fooo aacd09d8e2 chore: Support new error code (#7311)
* chore: fetch model list

* chore: suppor new error code
2025-02-03 20:52:08 +08:00
Peter Jose 25a27dfa81 chore(i18n): update id-ID translations (#7290)
- Translate 'themeMode' label
- Adjust text 'fontFamily'
- Update 'layoutDirection' translation
- Update 'textDirection' translation
2025-02-03 10:41:05 +08:00
Mohammad Mahdi Momeni 36349778e3 chore(i18n): update fa translations (#7292) 2025-02-03 10:05:34 +08:00
Nathan.fooo 9271d42db5 chore: fetch model list (#7306)
* chore: fetch model list
2025-02-01 23:48:32 +08:00
Richard Shiue 90d6e98b51 fix(flutter_mobile): drop focus on tap outside (#7274) 2025-01-24 09:23:52 +08:00
Lucas dd6b285cd3 chore: update CHANGELOG.md (#7276) 2025-01-23 17:55:12 +08:00
FakhriAzzouz 8b0d5d76a2 New Arabic Translations (#7260)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-23 14:02:32 +08:00
Morn 85b69bde15 chore: cancel button not visible in [Reset to Default] for shortcuts(#7171) (#7257) 2025-01-23 13:58:48 +08:00
Morn dd812d0501 chore: rename [font scale factor] to [scale factor] (#7267) 2025-01-23 13:58:09 +08:00
Richard Shiue a79f825ff1 chore: restrict uploadable image types (#7269)
* chore: restrict image format

* chore: code cleanup
2025-01-23 10:51:15 +08:00
Lucas 862e5629e3 fix: v0.8.2 launch review issues (#7266)
* fix: unable to copy table from Apple Notes

* fix: disable pasting a table into another table

* fix: ignore the delete shortcut if the next node is a simple table node
2025-01-22 22:46:03 +08:00
Richard Shiue c5a91e10df chore: support pasting image links that have no file extension (#7262)
* test: speed up copypasta tests

* chore: strip away markdown syntax if message is image only

* chore: paste image urls with no file extension

* test: add integration test

* test: group tests

* chore: apply code suggestions to 3 files
2025-01-22 22:13:18 +08:00
Lucas 0b0e10baa8 feat: turn -> into to → in document (#7256)
* chore: bump version 0.8.2

* feat: turn -> into to → in document

* Revert "chore: bump version 0.8.2"

This reverts commit 45efd4d7d7.

* test: add shortcut tests
2025-01-22 15:05:22 +08:00
Nathan ec18a3c443 chore: update docs 2025-01-22 09:47:06 +08:00
Nathan.fooo a0867ed688 chore: return ai image error (#7263) 2025-01-22 09:42:24 +08:00
Nathan.fooo cdf8e68ff2 Update plan desc (#7258)
* chore: update plan desc

* chore: update plan desc
2025-01-22 02:02:30 +08:00
Richard Shiue af92303e38 fix: launch review issues 0.8.2 (#7261) 2025-01-21 22:17:53 +08:00
Lucas 504041d75a chore: bump version 0.8.2 (#7255) 2025-01-21 13:31:40 +08:00
Lucas 06ab965413 chore: only enable document integrity check when enableDocumentInternalLog is on (#7251)
* chore: only enable document integrity check when enableDocumentInternalLog is on

* feat: copy divider and table from ChatGPT

* test: add copy from ChatGPT test

* feat: support copying link from keyboard clipboard
2025-01-21 13:29:31 +08:00
Morn 42bd4884fd feat: support custom image icon (#7236)
* feat: supporting upload custom image as icon

* feat: support custom image icon on mobile

* chore: clean code
2025-01-21 11:58:45 +08:00
Lucas 0c057243bc chore: bump client api (#7249)
* chore: bump client api

* chore: set default publish info to true
2025-01-21 09:18:25 +08:00
Lucas 8fdc6e9638 feat: support pasting table from notion, google docs and google sheet (#7247)
* feat: support pasting table from Notion

* test: add google docs / googles sheets table test

* fix: google docs test

* fix: paste table from notion test
2025-01-21 09:12:57 +08:00
Morn 187f7409ce feat: enable customizing icons for database views (#7187)
* feat: enable set icon for databse tab

* chore: resolve conflicts

* feat: enable changing dabtabse icon on mobile
2025-01-20 17:55:01 +08:00
Morn e3ce6e8b4b fix: use WindowSizeManager to zoom on mobile (#7215) 2025-01-20 17:54:26 +08:00
Richard Shiue cfe481759f chore: bump table column width when saving chat message to page (#7243)
* chore: bump table column width when saving chat message to page

* test: add unit test
2025-01-20 13:28:02 +08:00
Richard Shiue 6bcef33d05 chore: trim markdown text (#7242)
* chore: trim markdown text

* chore: trim before saving
2025-01-20 11:47:03 +08:00
Lucas aa8c9bad9f fix: remove app from homepage url (#7233)
* fix: remove app from namespace url

* fix: banner issue on mobile

* Revert "fix: remove app from namespace url"

This reverts commit 4bd5f3f590.

* fix: remove app from namespace url
2025-01-17 20:55:22 +08:00
Richard Shiue 1723886f3a feat: save messages as a new page (#7224) 2025-01-17 20:38:55 +08:00
FakhriAzzouz 63c7f7b6fe chore: update ar-SA translations (#7216)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-17 10:08:09 +08:00
Lucas f35dfaf525 fix: improve the document diff function to prevent partial ordering issues (#7217)
* fix: improve the document diff function to prevent partial ordering issues

* fix: improve the document diff function to prevent partial ordering issues

* fix: nested block padding issues

* fix: improve the document diff function to prevent partial ordering issues

* chore: update editor version

* test: add no diff test and update text diff test

* test: delete and insert text diff with different id

* test: insert single text / delete single text tests

* test: multiple delete and update diff

* test: multiple insert and update diff

* chore: revert cargo changes

* chore: remove unused code

* chore: optimize the code logic
2025-01-16 21:01:23 +08:00
Richard Shiue eead2d20f5 fix: pass alwaysDistributeColumnWidths to simple table block (#7228) 2025-01-16 16:42:24 +08:00
Richard Shiue f73c2540c6 chore: reorganize code (#7206) 2025-01-15 19:57:47 +08:00
Lucas 05c1924940 chore: update CHANGELOG.md (#7209)
* Revert "fix: disable deleting mutilple nodes in table"

This reverts commit 0507c39863.

* chore: bump version 0.8.1

* chore: remove unused tests
2025-01-14 21:04:00 +08:00
FakhriAzzouz b2f3f902b2 chore: update ar-SA translations (#7210)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-14 19:59:11 +08:00
Richard Shiue 33fbba18ca fix: message scrolling (#7207) 2025-01-14 16:37:13 +08:00
Nathan.fooo cea3c69239 chore: update plan desc (#7208) 2025-01-14 16:29:42 +08:00
Morn ff91c0a909 fix: correct the reference error of the icon widget (#7203) 2025-01-14 16:08:01 +08:00
Richard Shiue d1efda4c50 fix: launch review 0.8.1 (#7185)
* fix: launch review

* chore: scroll to end upon sending new message

* chore: bump editor version

* chore: scroll to bottom after adding message

* chore: code reorg

* chore: bump editor version

* chore: bump editor ver

* chore: bump editor

* chore: bump editor

* fix: file block node insertion

* fix: do the same thing on image

* chore: update icons and translations
2025-01-14 12:35:51 +08:00
Morn 2b1d1ba2f4 chore: remove iconContent from IconsData (#7199) 2025-01-14 09:31:43 +08:00
Richard Shiue 9a237b5f18 chore: allow customize that forces distribute column widths (#7198)
* chore: allow customize that forces distribute column widths

* fix: missing intrinsic width

* fix: what am i doing
2025-01-13 18:47:45 +08:00
Lucas fc21d1d245 fix: retry count should be clear if the value exceeds max retries (#7195)
* fix: retry count should be clear if the value exceeds max retries

* feat: add retry button when loading image failed
2025-01-13 17:17:05 +08:00
Morn 0c6d4df14b fix: icons not supported in callout(#7192) (#7197) 2025-01-13 17:14:30 +08:00
Morn 8df5d8dcb4 fix: icons not display in search results (#7189)
* fix: icons not display in search results

* chore: clean code
2025-01-13 14:35:29 +08:00
Lucas aa9f285f59 fix: search results were filtered accidentally (#7196) 2025-01-13 14:34:23 +08:00
Lucas 6173022a15 fix: 0.8.1 launch review issues (#7186)
* fix: Search result title appears empty

* chore: change "Type to search" to "Search or ask a question…"

* fix: the toolbar shouldn’t over the search panel

* feat: add hint text next to the web url

* test: the toolbar shouldn’t over the search panel

* test: search result title appears empty
2025-01-13 09:22:04 +08:00
Nathan.fooo afb479607b chore: bump client api (#7194) 2025-01-12 23:32:44 +08:00
Nathan.fooo 342c361184 chore: ai block with context (#7193)
* chore: ai block with context

* chore: fix test
2025-01-12 22:33:21 +08:00
Nathan.foooandLucas.Xu 790d5612f7 chore: retry load image (#7179)
* chore: retry load image

* feat: support retry count and retry duration in network image

* chore: use loading builder in network image

* feat: support retry logic in network image

* feat: disable image menu when loading image

* chore: error prompt

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-01-10 09:43:18 +08:00
Morn 99a4e330e8 feat: enable to reorder favorites (#7172) 2025-01-09 14:33:53 +08:00
Richard Shiue c3b702849f chore: remove error message when sending a new message (#7177) 2025-01-09 13:49:37 +08:00
Lucas a49acd9220 chore: bump version 0.8.1 (#7178) 2025-01-09 12:04:42 +08:00
Lucas dd3b4dc5fb feat: resize table column width on mobile (#7174)
* feat: support resizing column width of table on mobile

* feat: support highlighting border when resizing

* test: support resizing column width of table on mobile
2025-01-09 11:37:43 +08:00
Morn 155817a0f1 fix: navigation bar issue on linked pages(#7111) (#7142) 2025-01-09 11:27:37 +08:00
Morn 89c4629ec2 feat: support auto text direction on mobile (#7092) (#7163) 2025-01-09 11:26:59 +08:00
Changho Park 6ca81b3a66 chore: update translations for ko-KR (#7161)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-09 10:11:05 +08:00
FakhriAzzouz b278e14ffd chore: update ar-SA translations (#7164)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-09 10:07:38 +08:00
dawn cb6f7cb4b6 chore: update fr-CA translations (#7166)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-09 10:07:12 +08:00
mizou-soga f94feb780a chore: update ja-JP translations (#7167)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-09 10:07:01 +08:00
Lucas 0ef1b27ae5 fix: filter the search results by duplicate ids (#7173) 2025-01-08 19:33:24 +08:00
Lucas 64994b3336 chore: enable AI seach (#7169)
* chore: enable AI seach

* chore: remove unused code

* fix: replace the old web base url with the new one
2025-01-08 15:52:24 +08:00
Nathan.foooandLucas.Xu 6966b303ff chore: bump collab commit id (#7168)
* chore: bump collab commit id

* chore: bump collab version

* chore: bump collab version

* fix: disable deleting mutilple nodes in table

* chore: bump collab version

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2025-01-08 14:46:44 +08:00
ab8e01bbf7 feat(flutter): pre-defined response formats (#7128)
* feat: pre-defined response formats

* chore: adjust bottom sheet

* chore: rename and clean up enums

* chore: move all mobile input actions to the bottom

* chore: bump client-api

* chore: connect to API

* chore: apply suggestions from code review

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

* chore: code cleanup

* chore: code cleanup

* chore: update client-api

* chore: expand page

* chore: simplify logic for not displaying related questions

* chore: remove hover effect view icon in select sources

* chore: regenerate with different format

* chore: remove error messages when sending new one

* chore: code style

* chore: bump client api

* fix: image not displaying and hide editing options

* chore: don't fetch related questions for image only

* chore: fix clippy

* chore: don't add related questions on regenerate

* chore: bump editor

* fix: expand sidebar page

* chore: update client api

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: weidong fu <nathan@appflowy.io>
2025-01-08 10:43:03 +08:00
Richard Shiue e25633636b fix(flutter_mobile): linked grid cannot be displayed in document (#7162) 2025-01-07 17:56:17 +08:00
Lucas d87a209c4a fix: context menu should not auto show when collaborating (#7151)
* fix: context menu should not auto show when collaborating

* chore: update document_bloc.dart
2025-01-07 09:40:20 +08:00
Lucas a33cf1f488 fix: filter out the space when opening new tab (#7152)
* fix: filter out the space when opening tab

* test: filter out the space when opening tab
2025-01-06 17:10:18 +08:00
Lucas 91236006d4 feat: custom share url, publish url and copy link to share (#7061)
* feat: custom share url, publish url and copy link to share

* chore: update translation

* feat: support base share domain

* feat: customize web url in login page

* feat: support customizing web url on mobile

* test: change web url test

* fix: cloud integration test

* fix: integration test

* fix: integration test
2025-01-06 16:05:05 +08:00
Richard Shiue fd9baf7a06 chore: code cleanup (#7150) 2025-01-06 13:34:11 +08:00
Mathias Mogensen ed646ccba2 chore: add loose restrictions to date time text field (#7024)
* chore: add loose restrictions to date time text field

* chore: remove date formatter
2025-01-05 21:02:29 +08:00
Lucas 7bc358d7ac fix: simple table issues and locale issues (#7138)
* fix: can't make changes on row or column of table

* fix: fallback to en-US if the locale is invalid

* chore: remove unused code

* fix: simple table issues
2025-01-05 19:55:15 +08:00
Morn 14fc287071 fix: recent icon doesn't work in space icon (#7133) (#7137) 2025-01-05 19:54:59 +08:00
Morn cdf7dced8b fix: count title towards word count(#7042) (#7127) 2025-01-05 19:54:46 +08:00
Lucas 552c59218c fix: convert false value in attributes to null (#7135) 2025-01-03 15:55:25 +08:00
Richard Shiue c7e0e36902 feat(flutter_desktop): save ai message to page improvements (#7136)
* chore: expand current space in select sources or when saving to a page

* chore: hide non-document views while saving to page

* chore: add buttons when saving to page

* chore: adjust icon color

* chore: adjust translations

* chore: code cleanup
2025-01-03 14:01:19 +08:00
FakhriAzzouz 1f82c6682b chore: update ar-SA translations (#7129)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2025-01-03 10:12:30 +08:00
Richard Shiue 21261ee813 feat(flutter_mobile): adjust bottom sheet quick action button style (#7121)
* feat: adjust bottom sheet quick action button style

* chore: remove final dividers
2025-01-03 10:11:17 +08:00
Morn 15deb8ea79 fix: icon picker issues on mobile (#7113)
* fix: error displaying in Page style

* fix: error displaying in Favorite/Recent page

* fix: complete the filter logic of icon picker

* fix: the color picker showed when tapping down

* fix: icons are not supported in subpage blocks

* chore: add some tests

* fix: recent icons not working for grid header icon
2025-01-03 10:04:14 +08:00
Kilu.He f7f99a162e chore: remove web app (#7126)
* feat: support rename workspace on web

* chore: remove web app from repo
2025-01-03 09:24:45 +08:00
Richard ShiueandMathias Mogensen c05f19edd2 feat(flutter_desktop): add button to expand side peek (#7118)
* chore: update sidebar top bar color

* feat(flutter_desktop): button to expand side peek

* Update frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

* chore: fix comment

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
2025-01-02 15:51:40 +08:00
Richard Shiue 407e88b27f chore: bump dependencies (#7123)
* chore: bump dependencies

* test: fix unit test

* fix: downgrade percent indicator

* chore: flutter analyze
2025-01-02 15:19:00 +08:00
Kilu.He 512113877b feat: support create workspace on web (#7122) 2025-01-02 10:46:56 +08:00
Lucas 8f7cb50dd4 fix: simple table issues on mobile (#7115)
* fix: header row/column tap areas are too small on mobile

* test: header row/column tap areas are too small on mobile

* feat: enable auto scroll after inserting column or row

* fix: enter after emoji will create a softbreak on mobile

* fix: header row/column tap areas are too small on mobile

* fix: simple table alignment not work for item that wraps

* test: simple table alignment not work for item that wraps
2024-12-31 16:01:45 +08:00
Ahad PatelandLucas.Xu 7dedb84504 feat: i18n for duplicated field names (#6769)
* feat: i18n for duplicated field names

* fix: flutter analyze

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-31 15:54:02 +08:00
Lucas b2ca5c77f6 fix: simple tests on mobile (#7102)
* fix: simple tests on mobile

* fix: subpage block padding
2024-12-31 12:03:44 +08:00
Darío HereñúandLucas.Xu 11d720465b chore: update es-VE translations (#3645)
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-31 11:04:37 +08:00
FakhriAzzouz c2643bfb6c chore: update ar-SA translations (#7099)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-12-31 11:02:16 +08:00
Morn 3d4d4cf709 test: add edit link menu test (#7097) 2024-12-31 09:46:13 +08:00
Ahad PatelandLucas.Xu b965a5f3ae feat: delete the previous image when the cover changes in local mode (#6368)
* remove unecessary images from localstorage

* feat: Add handler for deleting previous cover image on cover image change

* fix: add local image case for versions after 0.5.5

* fix: add try catch block and delete action to bottom of function

* chore: add test case for uploading and deleting image in localmode

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-31 09:36:14 +08:00
KD-MM2andNathan.fooo 73463cd7e3 chore(i18n): update vi-VN translations Fink 🐦 (#6290)
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
2024-12-30 18:38:34 +08:00
Richard Shiue 6158954d97 test: fix change server integeration test (#7096) 2024-12-30 18:10:55 +08:00
Kilu.He 3190eebf6e fix: modified universal link (#7094) 2024-12-30 18:10:36 +08:00
Lucas dfe994b341 feat: auto-dismiss collapsed handle on Android if no interaction occurs (#7088)
* feat: support auto-dismiss collapsed handle on Android

* fix: hit test area of collasepd handle is too big

* chore: upgrade appflowy_editor

* fix: simple table issues on mobile

* feat: highlight cell after insertion

* test: text color and cell background color test

* fix: sign_in_page_settings_test
2024-12-30 17:56:43 +08:00
Richard ShiueandLucas 92722d0922 fix(flutter_desktop): workspace menu ui issues (#7091)
* fix(flutter_desktop): remove log out and workspace option popovers conflict

* test: add integration test

* fix(flutter_desktop): workspace list scrollbar overlaps with list

* chore(flutter_desktop): fix padding around import from notion button

* chore(flutter_desktop): adjust popover conflict rules for workspace

* test: add integration tests

* chore(flutter_desktop): make the popoovers as barriers

* fix: regression from making the workspace item menu as barrier

* chore: update frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart

Co-authored-by: Lucas <lucas.xu@appflowy.io>

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2024-12-30 17:55:40 +08:00
Lucas e04c1bdaec chore: update changelog (#7095) 2024-12-30 17:53:55 +08:00
Richard Shiue 20b16cf174 feat: add toast messages for ai chat interactions (#7086) 2024-12-30 17:17:36 +08:00
Morn 5ffa27f545 fix: toolbar menu not showing beacase keyboard height is not updated in time (#7060) 2024-12-30 16:57:59 +08:00
Morn 00cdbe5a1c fix: issues related to the emoji icon picker (#7063)
* fix: remove the scrolling conflict of the icon picker on macOS

* fix: the icon is not supported in sites tab

* feat: keep the icon panel open after click ramdom

* feat: the type of selector opened depends on the already set icon or emoji

* feat: the skin tone of the random emoji follows the selected skin ton

* fix: unit testing error
2024-12-30 16:42:14 +08:00
Richard Shiue dffb865a66 chore(flutter_desktop): adjust toast style (#7083) 2024-12-30 16:35:41 +08:00
Kilu.He 5e581e912f fix: hover controls position (#7093) 2024-12-30 15:51:58 +08:00
Kilu.He 256d015967 fix: add tip for database page (#7090) 2024-12-30 15:46:39 +08:00
Nathan.fooo d9b3f3f6c6 chore: remove tarui (#7089)
* chore: remove tarui

* chore: remove tarui config
2024-12-30 15:41:23 +08:00
Nathan.foooandLucas.Xu 09fa75f5ec chore: remove crates (#7085)
* chore: remove crate

* chore: use rust 1.81

* chore: remove build flag

* fix: macos build error

* chore: upgrade super_clipboard

* chore: fix pb lint warning

* chore: fix clippy

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-30 14:25:05 +08:00
Lucas 38c2937f64 fix: docker ci (#7087) 2024-12-30 13:35:37 +08:00
Richard Shiue 06d5bc734b fix: try to fix message regeneration being appended instead of replaced (#7084) 2024-12-30 12:38:56 +08:00
Nathan.fooo a521541cb7 chore: only sync when doucment was changed (#7081)
* chore: only sync when doucment was changed

* chore: fmt
2024-12-30 10:26:06 +08:00
Kilu.He 7d61252e6a feat: project management & document editor features on web (#7051)
* fix: support page operations

* fix: support select language of code block

* fix: support hover controls of document

* fix: support slash panel of document

* feat: support image block and file block of document

* feat: support outline block of document

* feat: support add document on document

* feat: support create sub-page on document

* feat: support mention panel

* feat: support more actions

* feat: support create space

* fix: add some shortcuts to document

* fix: add some shortcuts to document

* fix: multiple select blocks

* fix: reupdate trash after restore and delete

* fix: lint check

* fix: callout block

* fix: view icon

* fix: colors

* fix: colors

* feat: support add grid/board/calendar

* feat: support database new layout

* fix: slash panel

* fix: bugs

* fix: grid table

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: bugs

* fix: some bugs

* fix: some bugs

* fix: invite members

* fix: styles

* fix: styles

* fix: styles

* fix: inline code

* fix: image block

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: some bugs

* fix: add applink

* fix: add applink

* fix: add applink

* fix: add share link

* fix: inline link

* fix: add log

* fix: simple table bugs

* fix: simple table bugs

* fix: some bugs

* fix: build size

* fix: build size

* fix: remove private from package.json

* fix: support quick note

* fix: support quick note

* fix: support quick note

* fix: support quick note

* fix: support quick note

* fix: some bugs

* fix: support paste md

* fix: modified toolbar

* fix: modified toolbard actions

* fix: adjust editor toolbar

* fix: support billing
2024-12-30 10:12:54 +08:00
Lucas cb2b9ac537 fix: simple table issues on mobile (#7068)
* feat: use fixed height for table menu

* feat: do not close menu after enabling header column/row

* fix: remove cell background color

* fix: the text color doesn't apply to the heading block

* fix: replace quick action icon color

* fix: add done button in text color menu

* fix: don't access clipboard when opening table action menu

* fix: table navigation issues and reminder issue

* fix: cursor flash when create a row and column at the same time

* fix: unable to insert page sometimes

* fix: use default name if the title is empty

* chore: replace align icons
2024-12-30 09:23:03 +08:00
Alexandre Cirilo af31f8cf6f chore(i18n): update fr-FR translations 🐦 (#7076) 2024-12-30 09:16:52 +08:00
Richard Shiue 87c1245a3f fix: launch review issues 0.7.9 (#7073)
* fix: disable select sources when on local AI

* chore: tooltips for right sidebar actions

* chore: update selection after inserting into existing page

* fix: mobile select sources button background color

* fix: ai response metadata

* chore: don't update selected sources section until reopen

* chore: show views from all spaces in select sources menu

* chore: revert podfile changes

* chore: decrease spacing between metadata and hover actions

* chore: improve placeholder for ai message metadata
2024-12-29 21:48:37 +08:00
Nathan.fooo 49d9417cac refactor: crate dir (#7080) 2024-12-29 14:47:28 +08:00
jumbi77 1f29235538 chore(i18n): update de-DE translations 🐦 (#7071) 2024-12-28 15:39:34 +08:00
Richard Shiue fd1e36b21a fix: numbered list misalignment (#7066)
* fix: numbered list misalignment

* chore: fixed width numbers
2024-12-27 16:08:27 +08:00
Lucas 7db11c7cfd chore: fix release builder issue (#7058)
* chore: use --locked to install cargo-make

* chore: upgrade to macos 13
2024-12-26 14:03:23 +08:00
Lucas 33888d583b chore: bump version 0.7.9 (#7056) 2024-12-26 13:49:55 +08:00
3959cdba3a feat: add option to paste plain text (#7045)
* feat: add option to paste plain text

* refactor: optimize the code

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: Lucas <lucas.xu@appflowy.io>

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: Lucas <lucas.xu@appflowy.io>
2024-12-26 13:22:27 +08:00
Richard ShiueandLucas 200b367e4c feat: add ai message content to document (#7041)
* feat: add ai response content to page

* chore: apply suggestions from code review

Co-authored-by: Lucas <lucas.xu@appflowy.io>

* chore: apply suggestions from code review

* chore: reorganize code

* chore: i18n

* chore: enable opening the document in the sidebar

* fix: async await

* chore: rename ai message action bar widget

* feat: make transactions be reflected in the opened document

* chore: don't forget to close the bloc

* fix: isLastLineEmpty

* chore: code cleanup

* fix: sync after EditorState.apply

* chore: decrease visibility of DocumentBlocMap

* chore: add back missing assert

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2024-12-26 12:18:48 +08:00
Richard Shiue 956d2dfd07 fix(flutter_desktop): row detail checklist cell "create new" text field doesn't clear after clicking on create button (#7055)
* fix: clear text controller when clicking on add button no matter what

* test: add integration test
2024-12-26 12:18:17 +08:00
Lucas 83e50d376e feat: support plus menu in table cell on mobile (#7048)
* feat: support plus menu in table cell on mobile

* test: support plus menu in table cell on mobile

* feat: add lightImpact feedback

* chore: optimize the action sheet
2024-12-26 11:09:56 +08:00
Mathias Mogensen 802a667907 feat: drag view into document to link to view (#6991)
* feat: drag view into document

* chore: remove redundant method call
2024-12-26 09:57:01 +08:00
e67e9cc647 feat: support recent section in icon picker (#7015)
* fix: scroll event will be intercepted by tooltips

* fix: improve the logic to filter emojis or icons by keyword

* feat: add the recent icons and emojis to the selector

* refactor: optimize the code

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: LucasXu0 <lucas.xu@appflowy.io>

* fix: ensure the focus of emoji_search_bar not be lost within a second

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
Co-authored-by: LucasXu0 <lucas.xu@appflowy.io>
2024-12-26 09:55:29 +08:00
Lucas 07c4389f6a feat: refactor slash menu items to support showing in table cell (#7038)
* feat: refactor slash menu items to support showing in table cell

* chore: remove default slash menu items

* feat: support customizing slash commands in table cell

* fix: unable to delete math equation

* fix: underline issue

* feat: support text align in table

* fix: image alignment doesn't work in simple table cell

* fix: reduce the padding in table cell

* fix: drag column / row error in table

* test: support slash menu in table
2024-12-25 09:28:26 +08:00
Nathan.foooandMathias Mogensen 171c5634f2 fix: calculation UI refresh (#6615)
* chore: add test

* chore: update test

* chore: create events

* chore: load csv

* test: add events

* test: clean

* chore: fix test

* chore: fix test

* chore: fix test

* chore: fmt

---------

Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
2024-12-24 21:27:34 +08:00
Lucas d7bda10e6f fix: do not wrap the inline math equation in a new line (#7035)
* fix: do not wrap the inline math equation in new line

* test: inline equation text and inline text align
2024-12-24 09:39:24 +08:00
Lucas 20bff9003e feat: support table align on desktop (#7034)
* feat: support table align on desktop

* test: update table align

* test: add integration test
2024-12-24 09:39:15 +08:00
Lucas 6e0534400b feat: reordering table columns and rows on mobile (#7036)
* feat: support table overflow

* fix: heading padding

* feat: support reordering table

* feat: highlight cell border when dragging

* chore: optimize the highlight border render logic

* fix: table menu error

* test: insert a table / insert column / insert row test

* test: delete column / delete row

* test: delete column / delete row

* test: enable header row / header column

* test: clear content, bold text
2024-12-23 21:07:09 +08:00
Lucas 17c116a53b feat: support table block on mobile (#7001)
* feat: support inserting table from + menu

* feat: support row/column action sheet

* feat: highlight reorder button

* feat: support table action mobile

* feat: support insert row / insert column

* feat: add more actions in the table bottom sheet

* feat: implement the action logic for table action menu

* feat: support clear content

* feat: enable header column / header row

* fix: unable to cancel highlight status after closing table action menu

* fix: table cell parse error

* feat: support insert left/insert right

* feat: add delete in table action menu

* feat: support copy a table row/column

* feat: support copy paste in table cell

* feat: support cut in table

* feat: disable cut if no content in clipboard

* chore: refactor table action bottom sheet

* feat: use ISimpleTableBottomSheetActions to manage the mobile action bottom sheet

* feat: add bold/text color/text background color in table bottom sheet

* feat: add align in table bottom sheet

* feat: support bold/text color/text background color update command

* feat: add align action to table action menu

* feat: integrate bold command in table bottom sheet

* feat: integrate text color and cell background color command in table bottom sheet

* feat: integrate text color command and cell background color command in table action menu

* chore: remove unused code

* fix: page style json parse error

* feat: support copy link to block / duplicate in table action menu

* feat: support align in table action menu

* feat: add quick actions in table action menu

* chore: update translations

* feat: integrate copy paste in table action menu

* feat: integrate align in table action menu

* fix: flutter ci

* test: add copy/paste/cut test

* fix: align the table with the document title

* fix: flutter tests

* fix: flutter ci
2024-12-23 10:29:41 +08:00
Richard Shiue 27b769362b chore: remove unused response state (#7031) 2024-12-23 10:03:20 +08:00
Richard Shiue b8e7d57ee6 fix: regenerating the last answer doesn't work (#7029)
* fix: regenerate not working for last message

* chore: code cleanup
2024-12-22 14:47:11 +08:00
Richard Shiue ed052c6792 chore: include parent view id in initial chat settings (#7030)
* chore: include parent view id in initial chat settings

* chjore: add a tooltip
2024-12-22 14:46:54 +08:00
Richard Shiue 5cd2c63320 fix: update rag_ids not saving to local store (#7028)
* fix: update rag_ids not saving to local store

* chore: bump up send button size
2024-12-21 14:31:48 +08:00
FakhriAzzouz 15567a81e9 chore(i18n): update ar-SA translations (#7017)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-12-20 23:23:59 +08:00
Richard Shiue 7ab68dcc2c chore(flutter_desktop): ai chat ui improvements (#7025)
* chore: improve hover action bar inner radius

* chore: improve ai input appearance
2024-12-20 23:22:48 +08:00
Richard Shiue 30131fd9e4 chore: adjust select sources (#7019)
* chore: adjust select sources

* chore: restrict number of selected highest-level documents

* chore: ignore chat views

* chore: code cleanup
2024-12-20 22:43:26 +08:00
Richard Shiue ddcdd545d9 chore: regenerate response improvements (#7018)
* fix: disable sending message while streaming

* chore: don't allow regenerate while streaming
2024-12-20 11:42:43 +08:00
Nathan.fooo d25a399aba chore: sync chat document when open chat (#7016)
* chore: sync document

* chore: auto create chat title
2024-12-20 00:15:01 +08:00
ZackandLucas.Xu ee96a44fef feat: use new api to get the workspace member role (#6783)
* chore: update to latest client api

* chore: merge with main

* chore: remove unneeded code

* fix: sqlite migration

* fix: cargo fmt

* feat: use new api to get the member role

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-19 15:19:24 +08:00
Morn dda3962249 feat: combine icons and emojis into one menu(#6806) (#6998) 2024-12-19 15:19:10 +08:00
Morn c2743472bc fix: the index error when deleting text and then using the up or down arrow keys (#7009) 2024-12-19 14:40:11 +08:00
Richard Shiueandnathan e73fd56152 feat: regenerate ai response (#7006)
* feat: regenerate ai response

* chore: find question id instead of assuming

* chore: fix clippy

* chore: show local messages if they were there

* chore: remove duplicate code

* chore: fix loading message

* chore: revert unintended translation key removal

* chore: update translation for ai service unavailable

* chore: fix initial chat message load

---------

Co-authored-by: nathan <nathan@appflowy.io>
2024-12-19 14:13:53 +08:00
Richard Shiue 04a013f7ee chore: fix clippy (#7007) 2024-12-18 15:34:23 +08:00
Richard Shiue b966e3ea07 feat: select sources for ai prompt (#6975)
* feat: select sources for ai prompt

* feat: mobile

* chore: disable mention page

* chore: disable select sources when on local ai

* chore: code cleanup
2024-12-18 14:34:11 +08:00
Nathan.fooo 9d53f758d4 chore: bump client api 088 (#7004)
* chore: bump client api

* chore: bump client api

* chore: fix clippy
2024-12-18 12:12:58 +08:00
Lucas 0689f4e7e1 chore: bump version 0.7.8 (#7000)
* chore: bump version 0.7.8

* chore: use --locked to install duckscript
2024-12-17 17:59:27 +08:00
Lucas 867d515a35 feat: support readonly table (#6997)
* feat: support readonly table

* fix: cannot preview pasted images

* chore: remove http schema on android
2024-12-16 17:59:38 +08:00
Richard Shiue 381d946808 chore: filter out spaces from mention page (#6994) 2024-12-16 12:25:31 +08:00
Lucas f307300b96 fix: simple table issues (#6985)
* fix: list padding in table cell is too wide

* feat: improve tab in table cell

* feat: improve shift+tab in table cell

* fix: unable to edit cell after deleting an image

* fix: inline attribute issue

* fix: disable dragging a block into table

* feat: add distribute column evenly in column action menu

* fix: numbered list icon align in table cell

* feat: add setToPageWidth and distributeColumnEvenly in table menu

* feat: support highlight color

* chore: update editor version

* test: add setToPageWidth and distributeColumnEvenly in table menu

* test: inline attribute issues

* test: add distribute column evenly in column action menu

* test: select all in table

* test:  improve tab(+shift) shortcut in table cell

* test: improve enter shortcut in table cell

* feat: keep the same column width after using distribute column widths evenly

* test: keep the same column width after using distribute column widths evenly

* test: drag block to other block's child
2024-12-16 11:47:08 +08:00
Henri Devigne d74e7b63ca chore(i18n): update fr-FR translations 🐦 (#6987) 2024-12-16 11:10:14 +08:00
uxadax 792eec7d4b chore(i18n): update de-De translations (#6984)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-12-16 10:12:07 +08:00
Mathias Mogensen 67e93a12e6 fix: inline grid launch review (#6992) 2024-12-16 03:05:45 +01:00
Richard Shiue e188552c1e fix: ai chat initial rag_ids shouldn't include views that are not documents or in the trash (#6982)
* chore: only add document views to initial rag_ids

* chore: filter out views that are in the trash
2024-12-13 13:04:17 +08:00
Kilu.He ebb1b6dffb fix: simple table bugs (#6981) 2024-12-13 10:34:19 +08:00
Richard Shiue 25f9bee963 fix: launch review issues 0.7.8 (#6983)
* chore: mention popup height match design

* chore: adding while scrolling through mention page popup

* chore: don't add @ and restart creating mention when already in the middle of one

* fix: hide orphan views from mention page

* fix: arrow navigation wrap mention page popup breaks bloc
2024-12-13 10:20:56 +08:00
Lucas 6a6fac7f82 chore: bump version 0.7.8 (#6978) 2024-12-12 12:50:26 +08:00
Richard Shiue 555b4b48bb fix(flutter_desktop): cannot copy ai response with ctrl c (#6976) 2024-12-12 11:57:00 +08:00
Lucas 3522569f97 feat: support universal link / app link on mobile (#6973)
* feat: support universal link/app link

* feat: support app link on android
2024-12-12 10:03:21 +08:00
Morn 699ea150ce feat: able to select language for the code block by arrow keys (#6905) (#6964) 2024-12-12 09:27:24 +08:00
Mathias Mogensen e4385adfa9 fix: rename untitled view (#6789)
* fix: rename untitled view

* test: add cloud test

* chore: clean up code

* test: use nameOrDefault

* test: fix failing test

* test: fix wrong assumption
2024-12-12 02:21:23 +01:00
Mathias Mogensen 1d46923c47 feat: shrinkWrap grid in document (#6925)
* feat: shrinkWrap grid in document

* fix: clean up code and minor fixes

* test: add test w/ load more option

* fix: reinstate pageview & clean unused code

* fix: clean database tab bar view
2024-12-12 02:21:06 +01:00
Morn 0bf706f438 fix: Esc not working for Find-Replace menu(#6955) (#6965) 2024-12-12 08:18:57 +08:00
Richard Shiue 399b7dd682 test: attempt to fix flaky test (#6970) 2024-12-12 08:16:58 +08:00
Richard Shiue e8f2940024 fix(flutter): some ai chat bugs (#6969)
* chore: add hover effect and fix radius

* chore: open ref page on mobile
2024-12-11 21:48:38 +08:00
Lucas 62d5d66d20 feat: set table to page width (#6956)
* feat: set table to page width

* feat: expand the table based on the widht percentage

* test: set to page width

* feat: distribute columns evenly

* test: distribute columns evenly

* fix: border width
2024-12-11 16:37:28 +08:00
Lucas 8b672a159f feat: support multiple blocks operation (#6958)
* feat: support multiple blocks operation

* test: support multiple blocks operation
2024-12-11 16:37:07 +08:00
uxadax d68212f4ce chore(i18n): update de-DE translations 🐦 (#6938) 2024-12-11 15:30:52 +08:00
Lucas 592390dc84 feat: support multiple lines in table cell (#6931)
* feat: support multiple lines in table cell

* feat: add document validator

* fix: unable to delete a code block in table cell

* feat: drag to expand the table row

* fix: integration test

* feat: support drag to expand the table

* fix: integration test
2024-12-10 15:20:12 +08:00
Nathan.foooandLucas.Xu d2b2f17b1c chore: diagnose sync issues with sync.log file (#6950)
* chore: filter sync log

* chore: filter sync log

* chore: enable/disable sync log

* chore: enable/disable sync log

* chore: observer document and folder

* fix: integration test

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-12-10 10:02:48 +08:00
Richard Shiue 2c88653a69 feat(flutter_mobile): improve appearance of mention page selector (#6946)
* feat(flutter_mobile): improve appearance of mention page selector

* chore: move to use CustomScrollview
2024-12-09 17:20:39 +08:00
Lucas 45b4eb4b3a fix: hotfix issues for v0.7.7 (#6948)
* fix: include link preview block and file block in exported markdown

* test: include link preview block and file block in exported markdown

* chore: remove unused logs

* chore: update editor version

* fix: "+" menu should be close after pressing space

* test: cancel inline page reference menu by space

* chore: update editor version

* chore: remove unused logs
2024-12-09 16:19:37 +08:00
Richard Shiue 7b93bbe5ff fix(flutter_desktop): clicking on empty space when editing a cell sho… (#6949)
* fix(flutter_desktop): clicking on empty space when editing a cell shouldn't close event card

* test: fix integration tests
2024-12-09 15:13:37 +08:00
Ahad Patel d21c0c0dfc feat: add same delete design in database (#6620)
* add same delete design in database

* fix: remove padding when widget is null or function is null
2024-12-09 15:13:20 +08:00
Richard Shiue f5e46967ec fix(flutter): implement mention date transaction handler (#6933)
* fix: implement mention date transaction handler

* test: add integration tests

* chore: code cleanup

* chore: early return if null delta
2024-12-09 14:40:34 +08:00
Lucas e0885e2567 fix: image tests (#6928) 2024-12-09 09:17:07 +08:00
Richard Shiue 3b56887267 feat(flutter_desktop): AI chat refer to UI (#6930)
* chore: code cleanup

* feat(desktop): implement ai chat side panel

* chore: set min width for right side panel
2024-12-09 08:48:38 +08:00
Nathan.fooo b5d5312c70 chore: remove self-hosted runner (#6944) 2024-12-08 20:59:40 +08:00
Nathan.fooo 722b436cad chore: Ai chat context (#6929)
* chore: implement chat setting

* chore: clippy

* chore: rename

* chore: set rag_ids when creating a chat

* chore: clippy

* chore: fix test

* chore: fix test

* chore: fix test

* chore: clippy
2024-12-08 18:25:25 +08:00
Laura Bécognée bb50466aa9 chore(i18n): update fr-FR translations (#6936)
correct typo redémarer => redémarrer
2024-12-07 12:49:42 +08:00
Nathan.fooo da0395be5e chore: fetch rows by chunk size (#6934)
* chore: chunk size

* chore: chunk size
2024-12-07 00:02:56 +08:00
Lucas 9e82f3d7b8 fix: unable to open local file using afLaunchUrl function (#6927)
* fix: unable to open local file using afLaunchUrl function

* chore: use the latest api to open the local file

* chore: use the latest api to open the local file

* chore: use the latest api to open the local file

* test: add local paht regex test
2024-12-06 14:36:48 +08:00
Lucas 67fe0d6bfd feat: support column and row reordering in table (#6912)
* chore: update changelog

* feat: add draggable in table reorder button

* feat: support displaying text color, background color and font item in table cell

* feat: separate gestures for popup menu and drag operations

* feat: support feedback mode for table

* feat: build dummy node to render table feedback

* feat: disable column resize handle when dragging column

* feat: higtlight the cell border when dragging

* fix: unable to reorder in row

* fix: do not rebuild the reorder button when reordering

* feat: add reorder logic and tests

* feat: reorder column

* feat: reorder row

* test: reorder row

* fix: table attributes are broken after reordering

* chore: remove unused listerner

* chore: code refactor

* fix: remove unused code

* feat: support rendering table feedback

* fix: unit test
2024-12-06 09:22:32 +08:00
Nathan.fooo dddf5aa195 chore: move type option to collab repo (#6921)
* chore: bump collab

* chore: bump collab

* chore: fix test compile

* chore: fix test

* chore: remove numeric

* chore: fix media type option
2024-12-05 14:12:25 +08:00
Richard Shiue 92945cafdf fix: initial ai chat load (#6920) 2024-12-04 20:53:03 +08:00
Kilu.He 5cf6617231 fix: simple table width (#6918)
* fix: adjust min width

* fix: adjust simple table font size

* fix: do not need to run rust-ci and docker-ci when web codes have been changed
2024-12-04 16:16:48 +08:00
Richard Shiue 03c84ff8b5 feat: open ai response url source in browser (#6917) 2024-12-03 22:55:34 +08:00
Richard Shiue 0cf3ade332 fix(desktop): resize sidebar menu regression (#6897) 2024-12-03 22:20:30 +08:00
Richard Shiue 7c24b6feb0 feat: revamp mention page interactions in AI chat (#6896)
* chore: code cleanup

* chore: improve mention page ui

* chore: just use view pb

* chore: remove chat input menu style

* chore: code cleanup

* chore: rewrite and unify chat input action handler and bloc

* feat: improve appearance of mention page popup

* fix: misaligned emoji text
2024-12-03 22:20:14 +08:00
Lucas 687121ff14 chore: upgrade rust version to 1.80.1 (#6916) 2024-12-03 21:38:28 +08:00
Lucas e32c584b3b fix: remove extra padding in table on mobile (#6915) 2024-12-03 20:41:52 +08:00
Lucas 64e4416f54 chore: release version 0.7.6 (#6910)
* chore: update changelog

* feat: support displaying text color, background color and font item in table cell

* fix: integration test and unit test
2024-12-03 17:27:59 +08:00
Nathan.fooo ca195887e0 chore: bump collab (#6914)
* chore: bump collab

* chore: bump collab
2024-12-03 17:05:40 +08:00
Kilu.He f9a5458b94 feat: support simple table (#6911) 2024-12-03 15:33:51 +08:00
Lucas 244d072a65 fix: pressing enter on a collapsible list toggle adds an additional new line (#6909)
* fix: pressing Enter on a closed toggle list adds an additional new line

* test: pressing Enter on a closed toggle list adds an additional new line

* chore: upgrade appflowy_editor

* chore: rename table align
2024-12-03 13:56:15 +08:00
Lucas d9bc97e012 chore: remove rename dialog logic (#6906) 2024-12-02 20:01:40 +08:00
LucasandRichard Shiue e7491e5182 feat: simple table issues (#6871)
* fix: disable cut command in table cell

* feat: only keep the table cell content when coping text from table

* fix: focus on the first cell after inserting table

* test: focus on the first cell after inserting table

* feat: highlight the cell when editing

* test: highlight the cell when editing

* fix: creating a new row makes a cursor appear for a fraction of a second

* fix: add 4px between scroll bar and add row button

* chore: rename simple table components

* fix: select all in table cell block

* test: select all in table cell block

* feat: disable two-fingers resize in table cell

* feat: includ table when exporting markdown

* test: include table when exporting markdown

* feat: optimize add row button render logic

* chore: optimize hover button render logic

* fix: column button is not clickable

* fix: theme assertion

* feat: improve hovering logic

* fix: selection issue in table

* fix(flutter_desktop): popover conflicts on simple table

* feat: support table markdown import

* test: table cell isEditing test

* test: select all in table test

* fix: popover conflict in table action menu

* test: insert row, column, row/column in table

* test: delete row, column, row/column in table

* test: enable header column and header row in table

* test: duplicate/insert left/right/above/below in table

* chore: duplicate table in optin menu

* fix: integraion test

---------

Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
2024-12-02 17:50:32 +08:00
promto-c 550b8835c6 chore: update th-TH translations (#6898) 2024-12-02 14:42:40 +08:00
Mathias Mogensen 1851721d9a fix: tabs lr 0.7.6 (#6899)
* fix: tabs improvements launch review

* test: add tests for pin/unpin feature

* fix: failing test + cleanup
2024-12-02 00:52:36 +01:00
Mathias Mogensen 603d65a790 fix: mobile camera android permission (#6900) 2024-12-02 00:49:54 +01:00
Richard Shiue 0cba3f9e3f fix(mobile): lost initial scroll position on empty ai chat page (#6895) 2024-12-01 13:18:20 +08:00
promto-c 81960a7f05 chore(i18n): add th-TH translations (#6891)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-11-30 21:17:30 +08:00
Lucas 62c4a8c541 fix: unable to render code block in ai writer block (#6883)
* fix: unable to render code block in ai writer block

* feat: enter to generate ai result
2024-11-29 18:49:47 +08:00
Richard Shiueandnathan 510d8357ee fix: RwLock race condition during group controller instantiation (#6860)
* chore: code cleanup

* fix: view editor used during initialization

* fix: quick and dirty hack job

* test: add test

* chore: don't create separate mut var

* chore: docs

* fix: uninitialized group controllers

* chore: remove group

* chore: fix test

---------

Co-authored-by: nathan <nathan@appflowy.io>
2024-11-28 17:26:13 +08:00
Mathias Mogensen 4c6f6f14f3 fix: add camera permission for iOS (#6872) 2024-11-28 10:17:24 +01:00
Lucas 27af57289f chore: update version to 0.7.6 (#6870) 2024-11-28 14:04:22 +08:00
Lucas 17aa8c9036 feat: simple table redesign (#6831)
* feat: define the simple table block data strucuture

* fix: close keyboard when uploading file

* feat: integrate a simple table

* feat: support selectable mixin for table

* feat: render border for table

* feat: support add column/row

* feat: support add column and row

* feat: support add column and row button

* feat: support table border and cell border

* feat: use context to manage hovering status

* feat: add hover status for cell

* feat: add hover row action

* feat: support row/column hover action

* feat: support table more actions

* feat: support insert left/right/above/below actions

* feat: add reorder button

* feat: clear content in table

* feat: support header column and header row

* chore: refactor the table more actions

* feat: render border in cell

* feat: add align button

* feat: add align menu

* fix: unable to delete last column

* feat: support drag to resize

* feat: support resizing column and set limitation

* fix: unable to resize

* feat: support table cell align

* fix: type error

* feat: support table cell background color

* fix: enable header column/row bug

* feat: support table background color

* feat: support duplicate table without background color

* feat: support duplicate with column color

* feat: update column width

* feat: move insert operations to a new file

* fix: row more action position is not correct

* fix: delete row error

* feat: support highlight the border after selecting a column/row

* feat: support mapping the insertion color/align

* feat: support mapping the duplication color/align

* fix: delete column error

* feat: support duplication map

* feat: support duplicating column with color and align

* feat: support duplicating row with color and align

* chore: optimize the table_map_operations

* feat: support clear content / header / style operations

* chore: remove null value in table attributes

* fix: delete row issues

* fix: delete column issues

* fix: unable to clear content

* feat: support arrow key shortcuts

* feat: support tab shortcuts

* feat: support backspace key shortcuts

* feat: support table block selection

* feat: set text in header column / row to bold

* fix: flutter analyze

* chore: enable debug log in table

* test: simple table insert operation

* test: simple table delete operation

* test: simple table header operation

* test: simple table style operation - column width

* test: simple table style operation - background color and align

* test: simple table content operation - clear content

* test: simple table operations - insertion, deletion and duplication

* test: simple table operations - duplicate with background and align

* fix: hide the delete button if there is only one column or one row

* test: add integration test and i18n

* chore: update translations

* fix: drag visual issue

* fix: exclude the popup menu in table more actions

* feat: only show the add button when hovering on the edge
2024-11-28 13:58:00 +08:00
Mathias Mogensen c910bda534 fix: minor improvements to media interactions (#6867)
* fix: minor improvements to media interactions

* test: remove redundant test
2024-11-28 02:13:06 +01:00
Mathias Mogensen 8d01b96281 feat: pin tabs (#6869) 2024-11-28 01:20:47 +01:00
Mathias Mogensen 55f12d5358 feat: attachment mobile toolbar item (#6868) 2024-11-28 01:20:30 +01:00
Mathias Mogensen 018c146d72 fix: built in database height issue (#6866)
* fix: built in database height issue

* fix: use shrinkWrap to decide height

* fix: pageview for lazy rendering
2024-11-27 15:37:45 +01:00
Lucas 068ac0e992 feat: support markdown syntax in ai writer block (#6864)
* feat: support markdown syntax in ai writer block

* feat: support nested list

* test: add markdown text robot tests

* feat: support in-memory update in ai-writer block

* feat: support markdown in ask ai block

* feat: render ai result with appflowy_editor

* feat: support markdown syntax in ask ai block

* fix: selection after replace
2024-11-27 21:19:24 +08:00
Lucas b036129efb fix: multiple select menu display issue (#6861)
* chore: set the minimum windows size to 640

* feat: optimize text insertion

* fix: unable to scroll down in multiple select options
2024-11-27 09:56:28 +08:00
Richard Shiue b3c8eb151a chore: improve ai chat errors (#6851)
* chore: move margins to layout define

* chore: related question alignment

* chore: adjust vertical spacing around sources

* chore: scroll to bottom animation improvement

* chore: improve ai chat error handling
2024-11-25 18:45:59 +08:00
Lucas e86d584ea7 feat: support skipping in-memory update transaction (#6856)
* feat: support skipping in-memory update transaction

* fix: flutter analyze

* feat: add sentence mode

* test: support skipping in-memory update transaction

* test: add sentence mode

* test: add sentence mode (2)

* chore: set enableDocumentInternalLog to false

* fix: integration test

* fix: integration test
2024-11-25 17:55:15 +08:00
Richard Shiue e0226e54a5 chore: minor ai chat improvements (#6855)
* chore: animation alignment improvement

* fix: missing context in drop zone

* chore: increase minimum padding between chat and edge of screen

* chore: unify padding for ai message loading
2024-11-25 10:43:08 +08:00
Lucas bde1457524 feat: support click to create content inside empty toggle list (#6854)
* feat: support click to create content inside empty toggle list

* test: support click to create content inside empty toggle list

* fix: toggle list rtl issue

* chore: optimize cover title request node logic
2024-11-25 10:39:23 +08:00
Lucas 2ad2a79bd0 fix: macOS error 50 on uploading files (#6853) 2024-11-25 10:39:15 +08:00
Richard Shiue f013bb9d6e chore: re-add chat message animations (#6850)
* chore: re-add chat message animation

* chore: bump up message vertical padding
2024-11-24 22:43:45 +08:00
Nathan.fooo 1b4a723500 chore: only fetch billing info when current user is the workspace owner (#6847) 2024-11-23 13:25:42 +08:00
Nathan.fooo b5d2af3371 chore: parse chat response (#6843)
* chore: parse chat response

* chore: remove unrelated message

* chore: update readme
2024-11-23 09:48:24 +08:00
Nathan.fooo 4205a34f04 chore: delete upload task when upload size exceed limit (#6841)
* chore: delete upload tasks

* test: update

* chore: bump client api

* chore: fix test
2024-11-22 18:18:24 +08:00
Richard Shiue e86a9d697c chore: simplify chat user message bloc and widgets (#6836) 2024-11-20 13:13:38 +03:00
Richard Shiue f82dabcc75 chore: bump flutter chat ui version (#6835) 2024-11-20 15:47:35 +08:00
Mathias Mogensen 09717d92c5 chore: inno setup ignore version on .exe (#6745) 2024-11-20 10:03:57 +08:00
Lucas a0d8711d5c fix: close other tabs before switching workspace (#6830)
* fix: close other tabs before swtching workspace

* test: close other tabs before switching workspace

* chore: update release note

* test: close other tabs before switching workspace
2024-11-19 15:41:55 +08:00
Lucas c24b68481d feat: support creating subpage block in row detail page (#6824)
* feat: support creating subpage block in row detail page

* feat: hide the row page from sidebar

* test: support creating a sub-page block in row detail page

* fix: update drag block logic

* feat: support toggle heading in outline

* test: add toggle headings show in outline block test

* fix: unable to get focus when opening subpage from card
2024-11-19 09:58:04 +08:00
Richard Shiue df7fe9750d feat: wrap code block lines in AI chat (#6828) 2024-11-18 21:52:51 +08:00
Mathias Mogensen 9c22bb4fed fix: tab change speed + remove favorite option (#6819) 2024-11-18 17:11:39 +08:00
nathan 3803cf2506 chore: fix test 2024-11-18 16:47:45 +08:00
Nathan.fooo 51a11fbebd chore: fix upload file limit (#6825)
* chore: fix file upload limit

* chore: update test
2024-11-18 16:21:47 +08:00
Kilu.He cc7476c75b fix: add new row cover (#6826)
* fix: add new row cover

* fix: add default callout icon
2024-11-18 16:09:31 +08:00
Lucas 8a7cedd5b4 fix: cannot paste text into the title of the doc (#6821)
* fix: cannot paste text into the title of the doc

* test: add paste in title test
2024-11-18 14:44:02 +08:00
Lucas 7e528cf260 feat: support editing path name on mobile (#6798)
* feat: support editing path name on mobile

* chore: format code

* chore: update publish toast

* feat: optimize the toast for mobile more actions menu

* feat: optimize update path name logic

* test: add update path name test

* fix: integration test
2024-11-18 10:33:51 +08:00
Arya Pratap Singh 8a39ff0580 fix: turn into menu naming inconsistency issues (#6817) 2024-11-18 10:33:23 +08:00
Richard Shiue 710fbbdc08 fix: ai chat error message background color (#6810) 2024-11-17 16:04:43 +08:00
Richard Shiue 6c52896f99 chore(mobile): remove ai chat sample question autoscrolling behavior (#6793) 2024-11-17 15:36:16 +08:00
Kilu.He f526df98c7 fix: add template button to publish page (#6809) 2024-11-17 14:58:28 +08:00
Richard Shiue 521d74e082 chore: more ai ui fixes (#6799)
* chore: more ai ui fixes

* fix: messages arriving out of order

* chore: don't drop focus when clicking on sample questions
2024-11-15 17:31:51 +08:00
Lucas 225683562b fix: 0.7.4 launch review issues on mobile (#6795)
* feat: expand the hit test area for more button in space menu

* fix: contrast issue for the delte dialog text

* fix: stay in space menu after deleting a space

* fix: hide padding when space icon list is scrolled down

* feat: expand the hit test area for toggle button

* feat: remove open workspace success toast

* chore: update translations

* test: stay in space menu after deleting a space

* chore: enable android test

* fix: integration tests
2024-11-15 16:19:02 +08:00
Richard Shiue 9138787f86 chore: deprecate third party markdown (#6794) 2024-11-15 15:03:12 +08:00
Lucas 6ffb9e4d0f fix: launch 0.7.4 review issues on desktop (#6792)
* chore: enable document test runner

* fix: double menu showing in workspace menu

* feat: only display copy in ai message

* test: add test

* test: fix integration test

* fix: rust ci
2024-11-15 11:12:27 +08:00
Richard Shiue bced9327b1 feat: AI chat UI improvements (#6760)
* chore: fix typo

* chore: redundant check for cloud and use guard clause

* feat: improve ai prompt input ui

* feat: improve ai chat welcome page

* feat: improve loading status indicator

* feat: improve chat bubble avatars

* chore: clean up chat theme

* feat: improve sizing, padding and text styles

* feat: improve sugested follow up question style

* feat: improve error message style

* feat: improve mobile chat ui

* feat: add superman

* fix: cannot send new questions after 2000 character limit reached

* feat: ai response action bottom sheet

* chore: code cleanup

* feat: improve look of side panel

* chore: BIG ANNOUNCEMENT CITY

* fix: test session issues

* fix: launch review issues
2024-11-15 00:26:37 +08:00
Richard Shiue 8120656198 fix(mobile): selecting date from cupertino date widget resets time (#6791) 2024-11-15 00:26:17 +08:00
Richard ShiueandMathias Mogensen 941b7cf04c fix(flutter_desktop): document search (#6669)
* fix: double dispose on find menu

* fix: empty query not resetting search service

* fix: input focus getting lost after clicking button or pressing enter

* chore: remove unused focus node and text controller

* chore: bump appflowy editor

* chore: code cleanup

* chore: fix focus getting lost on submission

* fix: next match focuses on title after jumping

* chore: bump appflowy editor

* revert: unnecessary changes to FlowyFormTextInput

* fix: title requesting focus unexpectedly

* Update frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_title.dart

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>

* chore: merge conflicts

* chore: code cleanup

* test: add integration test

* fix: show replace menu icon color in dark mode

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
2024-11-14 22:02:34 +08:00
Mathias Mogensen 1952ef0853 fix: tab menu and tabbar improvements (#6785)
* fix: tab menu and tabbar improvements

* chore: update appflowy_editor

* test: tab menu test

* test: fix test after refactor
2024-11-14 06:55:09 +01:00
Lucas d9f2d14e99 chore: bump version 0.7.4 (#6787) 2024-11-14 13:36:13 +08:00
Lucas 555d08e8ce feat: add toggle heading in plus menu on mobile (#6784)
* chore: add logs in space bloc

* feat: add toggle headings in plus menu

* test: add toggle heading block test

* test: toogle heading 1 block test

* test: add toggle heading selection test

* fix: toggle headings test

* chore: update new toggle heading icons
2024-11-14 13:34:24 +08:00
Mathias Mogensen 76009613fe fix: mobile file upload menu (#6786) 2024-11-14 06:10:03 +01:00
复予 651938a322 chore: update zh-CN translations (#6717)
* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦

* chore: update translations with Fink 🐦
2024-11-14 09:31:25 +08:00
Lucas b86011aa94 feat: optimize the font menu speed (#6780) 2024-11-14 09:19:35 +08:00
nathan cf240a392b chore: replace with appflowy cloud main branch id 2024-11-14 00:56:51 +08:00
Nathan.fooo 28530722bc chore: bump client api (#6781)
* chore: bump client api

* chore: fix member count serde error
2024-11-14 00:51:07 +08:00
ZackandLucas.Xu 3b304747f2 feat: last publish name (#6766)
* chore: wip update client api

* feat: add unpublished information

* feat: keep the last publish name

* test: add re-publish test

* test: add empty name test

* chore: update cloud version to 0.7.6

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-11-13 20:36:35 +08:00
Lucas cd3be696dc feat: optimize the workspace menu loading speed (#6758)
* feat: optimize the workspace menu loading speed

* chore: add workspace menu animation

* feat: optimize workspace menu loading speed on mobile

* test: workspace member count test

* fix: rust ci
2024-11-13 20:36:15 +08:00
Lucas 57933736ea feat: support embed webp (#6775)
* feat: support uploading webp

* feat: support jpeg and bmp image format
2024-11-13 17:16:48 +08:00
Lucas a46550c250 feat: support managing a space on mobile (#6764)
* feat: support duplicating and deleting a space on mobile

* feat: support editing a space on mobile

* feat: support renaming space

* feat: add more space icons

* fix: unable to rename space

* fix: code review

* chore: remove ingore import flag for testing

* test: add create space test

* chore: enable cloud test

* test: fix space test

* test: add rename/duplicate/delete tests
2024-11-13 11:10:15 +08:00
Lucas eed3f489c5 chore: update template icon (#6767) 2024-11-12 16:58:41 +08:00
Lucas d268f8c715 feat: integrate codemagic for testing ios/android (#6719)
* fix: pages overflow when selecting homepage

* feat: integrate codemagic

* Revert "fix: pages overflow when selecting homepage"

This reverts commit 156882a9a7.

* chore: try to fix build

* chore: remove flutter analyuze

* chore: launch ios simulator

* fix: flutter version

* fix: integration tests on mobile

* fix: mobile tests

* test: fix page style test

* chore: enable ios ci

* chore: update codemagic token

* chore: update app_id
2024-11-12 09:06:30 +08:00
Richard Shiue 3cd26cca35 fix(flutter_desktop): hidden kanban group name overflow (#6763) 2024-11-11 17:21:24 +08:00
Zack 6ad303583b feat: include member count in get_workspace API (#6747)
* feat: add member count for workspace

* fix: sqlite migration
2024-11-08 16:01:39 +08:00
Lucas e527a1843e chore: bump version 0.7.3 (#6743)
* chore: update changelog

* feat: add 'custom namespace' in plan description
2024-11-08 10:13:17 +08:00
Richard Shiue 3f8eb70ff3 chore: bump collab (#6746) 2024-11-08 02:33:28 +08:00
Richard ShiueandMathias Mogensen bd7976d005 fix: v0.7.3 additional launch review session issues (#6730)
* fix: focusedDay on is range toggle

* fix: attempt to fix date reminder

* chore: alignment issue on windows

* chore: use onFailure

* chore: default for set reminder

* chore: code style

* fix: add reminder bug

* fix: checklist item alignment

* fix: date cell reminders

* chore: regard cell datetime as correct

* fix: reminder creation date

* chore: bump collab that fixes reminders

* test: add date cell and reminder bloc test

* chore: bump collab

* chore: revert visual density change

* fix: submitting date time text field makes it flash

* fix: improve behavior of phantom checklist item

---------

Co-authored-by: Mathias Mogensen <mathias@appflowy.io>
2024-11-07 18:33:12 +08:00
Lucas 4e1532af3e fix: application window is not visible on Windows (#6740)
* fix: try to fix the windows not visible issue

* chore: update win32_window

* chore: adjust the new api
2024-11-07 15:55:38 +08:00
Mathias Mogensen 97999aee44 fix: block option interceptor (#6739)
* fix: use unique key in interceptor

* test: add block option interaction tests
2024-11-07 09:47:47 +08:00
Nathan.fooo 6785104c3a chore: send notification for file upload state (#6738)
* chore: send notification for file upload state

* chore: fix test
2024-11-07 00:00:54 +08:00
Mathias Mogensen 193c8242e2 fix: mobile media cleanup (#6733) 2024-11-06 08:03:20 +01:00
Kilu.He 59eda5e038 fix: modified invitation page text (#6734) 2024-11-06 13:36:27 +08:00
Nathan.fooo 3c8de8a52c chore: update upload file logs (#6735) 2024-11-06 11:18:40 +08:00
Zack d38a5f38e3 chore: update dep (#6727) 2024-11-06 09:12:16 +08:00
Lucas fe4f85a597 fix: align the namespace and toggle shortcuts (#6726)
* chore: update translations, url to URL

* fix: children disapper when using toggle shortcuts

* fix: add tooltip for namespace button

* test: add toggle shortcut test
2024-11-06 09:08:59 +08:00
Mathias Mogensen 0a1af4e61f fix: 0.7.3 improvements (#6731)
* fix: recheck workspace usage on workspace changed

* fix: rename insert page item
2024-11-06 00:50:27 +01:00
Kilu.He f6e002edbd feat: support toggle heading (#6712)
* feat: support toggle heading

* fix: support others markdown
2024-11-05 14:06:38 +08:00
Lucas 82effbf8e4 fix: toggle heading issues (#6714)
* fix: pages overflow when selecting homepage

* fix: toggle heading issues

* Revert "fix: pages overflow when selecting homepage"

This reverts commit 156882a9a7.

* chore: optimize code logic

* fix: assertion in toggle list

* fix: make the turn into menu and color menu exclusive

* test: add toggle heading test
2024-11-05 10:52:22 +08:00
Lucas 54096b391f fix: settings site issues (#6701)
* fix: pages overflow when selecting homepage

* fix: settings site issues

* chore: try to fix windows ci

* test: add tests

* fix: shareblock state update

* fix: pages overflow when selecting homepage

* fix: view name doesn't update after publishing

* chore: add translations

* feat: close popup menu after unpublishing page

* feat: align the published page name with header
2024-11-05 09:23:38 +08:00
Mathias Mogensen 39eee12f53 fix: launch review 0.7.3 (#6718) 2024-11-05 00:39:00 +01:00
Mathias Mogensen bae19b9c3a feat: files media improvements (#6632)
* feat: files & media uiux improvements

* feat: desktop grid media cell changes

* test: amend tests

* feat: media mobile improvements

* feat: more refinement

* fix: dart linter

* chore: update collab

* fix: icon and test

* test: refactor upload hint

* test: forgot to use helper method
2024-11-05 00:13:19 +01:00
Richard Shiue f00e1ebf20 chore: remove focus when starting to create a card from the bottom (#6709) 2024-11-04 14:30:32 +08:00
Kilu.He 939e28d2e1 feat: support tab and shift+tab on range (#6703)
* feat: support tab and shift+tab on range

* feat: add login to no found page
2024-11-04 14:14:16 +08:00
Nathan.fooo 3fbdcab7b0 chore: try to fix when load data from cloud cause windows delay rendering (#6708) 2024-11-04 13:53:02 +08:00
Nathan.fooo a1d0dba0a5 chore: remove local ai watch on windows and linux (#6704)
* chore: remove local ai watch on windows and linux

* chore: clippy
2024-11-04 11:26:05 +08:00
Mathias Mogensen 5e41849f69 fix: remove listeners (#6686) 2024-11-04 10:26:48 +08:00
Richard Shiue cf56e20be9 chore: launch review 0.7.3 (#6698)
* refactor: date picker

* chore: provide guidance to users while using date picker

* fix: row card icon alignment

* fix: untitled database views

* chore: hide hint text while choosing date range

* test: fix widget test

* chore: use current time when toggling include time

* chore: move autofill date logic to date picker

* test: add tests

* chore: also apply to mention date block

* test: fix integration tests

* chore: fix a date picker edge case

* fix: unmatching border radii
2024-11-04 10:11:56 +08:00
Nathan.fooo 1b9b2a5f8d chore: fix database view event (#6697) 2024-11-01 21:48:25 +08:00
Nathan.fooo 690b46a59a chore: fix remove view desc test (#6696)
* chore: fix remove view desc test

* chore: fix remove view desc test

* chore: clippy
2024-11-01 15:57:58 +08:00
Richard Shiue 4d33c87b33 test: fix date picker tests (#6695) 2024-11-01 08:40:43 +01:00
Mathias Mogensen 7cf0c4b16b fix: do not rely on getIt in turnIntoBlock (#6693) 2024-11-01 08:04:02 +01:00
Lucas 910b643db0 chore: bump version 0.7.3 (#6692)
* chore: bump version 0.7.3

* fix: windows ci
2024-11-01 14:23:09 +08:00
Nathan.fooo 0e5ff844b6 chore: remove view desc (#6691)
* chore: remove desc in view

* chore: remove desc in view
2024-11-01 14:18:35 +08:00
Ahad PatelandLucas a9a784f0f0 fix: owner can delete spaces created by other users (#6672)
* fix: owner can delete spaces created by other users

* fix: Update frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart

Co-authored-by: Lucas <lucas.xu@appflowy.io>

* fix: add import statement and remove conficting code

---------

Co-authored-by: Lucas <lucas.xu@appflowy.io>
2024-11-01 12:56:52 +08:00
Mathias Mogensen af390f584e feat: turn into page (sub page block) (#6685)
* feat: turn into page (sub page block)

* fix: bugs and node copyWith

* test: add base test
2024-11-01 05:14:51 +01:00
Lucas eff37d74dc fix: date reminder test (#6681) 2024-11-01 09:33:12 +08:00
Mathias Mogensen 0ea2da424f fix: reminder improvements (#6680) 2024-11-01 00:36:14 +01:00
Lucas 07e34609e7 feat: custom namespace/domain (#6614) 2024-10-31 14:38:32 +08:00
Lucas 74c757d0c8 feat: enable link to database in slash menu (#6673)
* feat: enable link to database in slash menu

* test: add link to page test

* test: remove improper test
2024-10-31 14:12:19 +08:00
Nathan.fooo e6357a9d6c chore: optimize import appflowy data (#6671)
* chore: only import user workspace

* chore: apply migration if need

* chore remove unused code

* chore: store first time installed version

* chore: clippy
2024-10-31 13:02:54 +08:00
Mathias Mogensen 5a047b9d2d fix: add referenced page hint (#6679) 2024-10-31 04:25:26 +01:00
Lucas 873ab6cdc7 feat: scroll to block after selecting notification item (#6667)
* fix: scrollbar's ScrollController has no ScrollPosition attached

* feat: support scrolling to block after selecting notification item

* chore: remove debug print

* fix: unable to cancel block selection
2024-10-31 10:29:34 +08:00
Mathias Mogensen af6736d352 fix: enable sub page block (#6595)
* fix: enable sub page block

* fix: open newly inserted page

* fix: created view should have empty name

* test: use secondary to rename page

* fix: make popover secondary interaction better

* test: amend test

* fix: icon color of sub page block

* test: fix tests

* test: fix hover issue

* feat: clean API for show at cursor on popover
2024-10-30 22:29:01 +01:00
Richard Shiue d5c1955ea3 chore: capitalization for field editor insert left/right (#6676) 2024-10-31 00:12:19 +08:00
Richard Shiue 8bb8131dd4 feat(flutter_desktop): press enter to confirm a dialog (#6675)
* feat: enter to confirm in popup dialog

* test: add test
2024-10-30 23:20:12 +08:00
Richard Shiue 7f4abb5866 chore(flutter_desktop): drop down menu conforms to design (#6674) 2024-10-30 23:19:05 +08:00
Richard Shiue fbe87cc536 fix(flutter_desktop): add forgotten call to update text cell (#6668) 2024-10-30 19:31:10 +08:00
Kilu.He 74165ace0f fix: optimize bundle (#6666)
* feat: open editing

* fix: use cdn to replace build react and react-dom

* fix: replace mention page icon

* fix: replace optimize web bundle
2024-10-30 14:13:52 +08:00
proxycon b576287ac6 chore: update translations with Fink 🐦 (#6656) 2024-10-30 09:29:05 +08:00
Richard Shiue 68d7211735 fix(flutter_desktop): glitchy kanban card editing (#6661)
* test: add test

* fix(flutter_desktop): editing kanban card is glitchy
2024-10-29 22:46:22 +08:00
Richard Shiue dd0dcace87 chore(flutter_desktop): set VisualDensity.standard for entire desktop app (#6648)
* chore: set standard visual density for entire app

* fix: pixel overflow

* fix: pixel overflow

* fix: pixel overflow

* fix: pixel overflow

* fix: text button padding

* chore: slash menu fixes

* chore: slash menu fixes

* ditto
2024-10-29 21:26:04 +08:00
Richard Shiue 8f58c39448 chore(flutter_desktop): improve title and heading appearance (#6658) 2024-10-29 21:25:13 +08:00
Nathan.fooo cee0e2ed42 test: fix ci (#6657) 2024-10-29 20:55:37 +08:00
Lucas 557ce7e006 chore: update appflowy_editor version (#6655)
* fix: foramt link starting at 0 index will lose href attr

* fix: pillcrow icon resize

* fix: integration test

* fix: emoji may not align on Linux
2024-10-29 19:39:56 +08:00
Nathan.fooo d50521d9b6 chore: ignore invalid database when import appflowy data (#6654) 2024-10-29 16:20:05 +08:00
Lucas 28aa2329fb feat: run integration tests on multiple runners (#6637)
* feat: split the integration tests into multiple runners

* feat: use 6 runners

* fix: name

* fix: integration tests

* fix: macos arm64 build

* Revert "fix: macos arm64 build"

This reverts commit 80b961361f.

* chore: use 7 runners

* chore: use 8 runners
2024-10-29 14:55:20 +08:00
Nathan.fooo 9707148b86 chore: bump appflowy collab (#6649)
* chore: fix test

* chore: fix duplicated view name

* chore: fix windows build

* chore: clippy
2024-10-29 11:21:49 +08:00
Lucas 4e739c857b fix: macos arm64 build (#6650) 2024-10-29 10:20:48 +08:00
Nathan.fooo 363fecc7d6 chore: bump client api that includes upload large zip file (#6634)
* chore: bump client api

* chore: update cloud version on CI

* chore: bump client api

* chore: update ci
2024-10-28 21:51:07 +08:00
Kilu.He 6bbaad59a4 fix: redirect to default page (#6638)
* feat: open editing

* fix: add logs

* fix: add logs
2024-10-28 17:55:11 +08:00
Kilu.He 497fcf3bbb fix: support redirect to default page (#6593) 2024-10-28 14:15:59 +08:00
Kilu.He b9ec9f863d fix: import interfaces and error message (#6630) 2024-10-28 11:34:56 +08:00
rensawamo eafd0b3353 fix: crash on delete button press caused by GoRouter (#6629)
* fix: crash on delete button press caused by GoRouter

* fix: gorouter pop
2024-10-28 09:09:11 +08:00
Nathan.fooo a42b6e02ab chore: revamp error for FolderCloudService service (#6623)
* chore: revamp error for FolderCloudService service

* chore: bump cloud

* ci: update configuration

* chore: clippy

* chore: bump client api
2024-10-26 11:20:16 +08:00
Mathias Mogensen 79116e7c7f fix: use drop path rather than node (#6611) 2024-10-25 14:05:19 +02:00
Richard Shiue 128ff594be fix: date picker code improvements and regressions (#6619)
* chore: improve handling result's error variant

* chore: dont call setState in didUpdateWidget

* fix: don't make assumptions on provided data
2024-10-24 20:46:45 +08:00
Lucas c821454df8 Revert "feat: custom domain (#6518)" (#6613)
This reverts commit 73e8b476fb.
2024-10-24 11:10:59 +08:00
ZackandLucas.Xu 73e8b476fb feat: custom domain (#6518)
* feat: api for namespace setting

* feat: add publish timestamp

* feat: update api

* chore: update proto

* feat: add link impl for appflowy cloud

* feat: impl set and get default view for workspace

* chore: merge with main and update to latest cloud client api

* fix: ci build

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-10-23 19:41:46 +08:00
Lucas faa95ffb9c feat: support toggle heading (#6598)
* feat: support toggle heading

* feat: add numbered list shortcut and heading shortcut

* feat: update format greater to toggle list shortcut

* feat: add removeToggleHeadingStyle shortcut

* feat: add toggle list and toggle headings in turn into menu

* feat: turn into toggle heading block

* feat: highlight selected toggle heading

* fix: collapsed button animation

* test: turn heading block to toggle heading block

* test: turn toggle heading block to heading block

* test: convert '# + >' or '> + #' to toggle heading block

* test: turn heading 2 block with following heading 1 block to toggle heading 2 block

* test: integration tests

* fix: unable to move children after heading 1 to toggle heading block

* test: unable to move children after heading 1 to toggle heading block

* fix: flutter analyze

* fix: toggle heading block test: insert toggle heading 1 - 3 block by shortcuts

* fix: inline database in document tests

* fix: integration test
2024-10-23 19:41:12 +08:00
Richard Shiue 6a5c4c95b7 feat(flutter): date picker improvements (#6582)
* feat(flutter): improve date picker

* test: add widget tests

* test: add bloc test

* test: adjust rust-lib tests

* fix: fix failing tests

* chore: fix rustfmt

* test: remove redundant await

* fix: fix failing tests

* feat: use cupertino date picker on mobile

* fix: flutter analyzer
2024-10-23 08:51:37 +08:00
Richard Shiue 7141e9d8cf fix(flutter-moile): edit primary field from database controls (#6591) 2024-10-22 20:22:15 +08:00
Richard Shiue d7bbb4261c chore: remove legacy CreateRowParams (#6592) 2024-10-22 20:22:03 +08:00
Lucas 251b16cb62 chore: update changelog (#6596) 2024-10-22 10:22:25 +08:00
Mathias Mogensen da80c599bf fix: show default view name (#6571) 2024-10-22 09:31:14 +08:00
Lucas ca6a900874 fix: display paragraph mark only if the block content is empty (#6594) 2024-10-21 20:25:55 +08:00
Lucas 5e09e96f7b fix: callout emoji size should follow the document font size (#6590) 2024-10-21 18:00:32 +08:00
Mathias MogensenandLucas.Xu 068500df84 feat: inline sub page mention (#6567)
* feat: inline sub page mention

* fix: disable editing documents in trash

* fix: duplicate block behavior

* refactor: clean up code

* feat: use formatText function instead of modify delta manually

* fix: paste behavior format mention

* fix: default icon for mentioned pages

* fix: view new parent turn into page reference

* test: add base test

* chore: add feature flag

* chore: default flag to on

* fix: minor fixes to behavior

* fix: review and code cleanup

* fix: dart linter

* fix: content is required

* test: use doc title to rename page

* test: add test coverage

* test: fix wrong expect

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-10-21 16:34:30 +08:00
Murat Sarıbaş 15949a7e21 chore: update translations with Fink 🐦 (#6565) 2024-10-21 09:45:52 +08:00
Mathias Mogensen e635fe3378 fix: trash breadcrumb line height (#6588) 2024-10-21 09:42:48 +08:00
Mathias Mogensen 8276c5db57 fix: drag and drop row document full page (#6589) 2024-10-21 09:42:15 +08:00
Mathias Mogensen 1d9876a6eb fix: notifier disposed but in use (#6586) 2024-10-21 00:21:25 +02:00
Lucas 6340174927 fix: share link and publish issues on mobile (#6579)
* fix: cache publish info to avoid repeated requests

* chore: 'Copy Link' to 'Copy link'

* chore: enable ios ci

* fix: ios ci

* chore: replace mention block icon

* fix: ios ci tests

* fix: ios ci tests

* fix: ai wirte replace assertion

* fix: windows ci
2024-10-20 10:13:06 +08:00
Lucas 7b031b228e fix: turn into issues (#6576)
* fix: cover title issues

* fix: the selection should be cleared if selecting child node

* fix: exclude the blocks that are not supported in the 'turn into' types

* fix: add logs

* fix: floating toolbar ai status

* test: selecting the parent should deselect all the child nodes as well

* chore: 'Copy Link' to 'Copy link'

* fix: select all and turn into block doesn't work on Windows

* test: calculate turn into selection test

* fix: option button tests
2024-10-18 17:13:38 +08:00
Richard Shiue ec408940e4 fix(flutter_desktop): bring back the board scrollbar (#6580) 2024-10-18 15:52:32 +08:00
Richard Shiue bd46fc11f4 fix(flutter-desktop): launch review issues for 0.7.2 (#6577)
* test: add test case for calendar filter

* fix: select option filter logic

* fix: calendar filters not applying if switched repeatedly

* fix: checklist cell editor improvements

* fix: nested scrolling in checklist

* fix: make filter logic match notion

* test: fix checklist cell test

* test: single select filter tests

* test: fix flutter test

* test: fix rust tests

* chore: fix clippy
2024-10-18 13:48:50 +08:00
nathan 42e55fe248 chore: update import string 2024-10-17 16:03:22 +08:00
nathan 3a9011b3cb chore: remove timezone for self-hosted runner 2024-10-17 15:42:01 +08:00
Lucas e3bf0442e8 fix: space icon color assertion (#6570)
* fix: space icon color assertion

* chore: bump version 0.7.2
2024-10-17 13:43:14 +08:00
Lucas 0413100e2b feat: observe mention block change and support block navigation (#6568)
* feat: observe mentioned block changes and navigate to block

* test: add delete mentioned block test

* chore: update editor version

* feat: navigate block in same page

* fix: sometimes turn into menu doesn't work

* test: add test

* fix: integration test
2024-10-17 13:29:34 +08:00
nathan 7cad04bbf4 chore: bump collab which include fixing windows build 2024-10-17 13:27:05 +08:00
Mathias Mogensen c9f0bbf26a feat: right click to open view more actions (#6529)
* feat: right click to open view more actions

* test: fix tests

* test: fix failing test

* fix: hide in move to page menu

* test: add simple test

* test: add negative case in cloud test

* chore: check code related to sidebar spaces

* fix: view item

* fix: clean up
2024-10-17 06:58:01 +02:00
Nathan.fooo d91639172a chore: show import error (#6569) 2024-10-17 10:32:51 +08:00
Nathan.fooo 5f0cc1becd chore: bump client api and collab (#6563)
* chore: bump client api and collab

* chore: fix windows build
2024-10-17 09:45:45 +08:00
Lucas a8bcab7770 feat: turn into multiple lines (#6558)
* feat: select multiple lines with block selection style

* feat: multiple nodes conversion

* fix: exclude children for the block can't contain children

* chore: update editor version

* fix: unit test

* test: convert nested list to heading/quote/callout

* test: transform nodes at the same level into another block type

* test: add undo redo for turn into

* test: add multi lines integration test

* chore: remove debug logs

* fix: integration test
2024-10-16 22:19:18 +08:00
Kilu.He b1682e4f54 fix: modified import function (#6562) 2024-10-16 21:40:32 +08:00
Richard Shiue 2447b34107 fix: text button's text style (#6559) 2024-10-16 17:06:14 +08:00
Richard Shiue 8126d19682 fix(flutter): wrong time zone when filtering by date field (#6543)
* fix: wrong time zone when filtering by date field

* test: set time zone
2024-10-16 16:44:06 +08:00
Richard Shiue 6cc8c81e3d chore: adjust Toggle widget's onChanged callback (#6556) 2024-10-16 14:39:46 +08:00
Nathan.fooo e86d812ee6 chore: add tooltip when import notion file (#6557) 2024-10-16 13:55:21 +08:00
Nathan.fooo c6f042830f fix: import document that contains refs (#6551)
* fix: import document that contains refs

* chore: clippy
2024-10-15 23:55:27 +08:00
Nathan.fooo 2378c0c441 chore: add calculation rust test (#6547)
* chore: add test

* chore: clippy
2024-10-15 17:20:53 +08:00
Lucas 7bc53d7bc6 feat: support 'share link' and 'publish' on mobile (#6548)
* feat: support publish on mobile

* chore: replace icons

* feat: support publish and copy link on mobile

* feat: support copy link

* feat: support copy share link

* chore: update icons

* fix: flutter analyze

* fix: ignore opening same page on mobile

* test: add copy link test on mobile
2024-10-15 15:12:59 +08:00
Nathan.fooo 7918d6c0d1 chore: update logs (#6550) 2024-10-15 15:05:22 +08:00
Kilu.He 073e8b897b feat: support import notion (#6549)
* feat: support import notion

* feat: support import page
2024-10-15 11:49:38 +08:00
Nathan.fooo 46a3006f4d refactor: database test (#6544)
* chore: remove script

* chore: remove script

* chore: refactor test

* chore: clippy

* chore: fix test

* chore: fix test

* chore: fmt
2024-10-15 09:32:06 +08:00
Deep Isane 5d578ab9e6 chore: update README.md fixed a typo (#6546)
Corrected "Acknowledgements" to "Acknowledgments" in [README.md].

This pull request addresses a minor typo found in the repository. The typo has been corrected to improve clarity and maintain the quality of the documentation.

This change is purely cosmetic and does not affect functionality.
2024-10-14 22:11:34 +02:00
Lucas f3fe729900 fix: potentially missing field in todo list (#6540)
* fix: potentially missing field in todo list

* chore: upgrade appflowy_editor_plugins

* fix: potentially missing field in todo list
2024-10-14 20:54:13 +08:00
Ahad PatelandLucas.Xu a04c157770 fix: date issue when enbling time feild (#6447)
* fix: date issue when enbling time field

* chore: add integration test for date or reminder field

* chore: add test file to document_test_runner.dart

* fix: flutter analyze

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-10-14 20:54:03 +08:00
Nathan.fooo 320ffcb434 Import notion zip (#6528)
* chore: upgrade client api

* chore: import notion zip

* chore: bump client api

* chore: bump collab

* chore: upgrade cloud version

* chore: fix test

* chore: fix test
2024-10-14 20:38:04 +08:00
Lucas 37a2fb17b5 feat: copy link to block (#6523)
* feat: copy link to block

* chore: add comment

* feat: using mention bloc to manage the mention bloc status

* feat: use mention page bloc to manage mentioned page status

* feat: observe mention block content changes

* feat: sync the block content

* fix: integration test

* fix: mentioned block display name contains unnessary -

* fix: handle block was deleted case

* chore: move the get doc block and get mentioned page status to service

* feat: support copy link to page

* test: add copy link to block tests

* test: add copy link to block(same pge) tests

* test: open the mentioned page

* fix: integration test

* Revert "fix: integration test"

This reverts commit f4466b22d8.

* fix: integration test
2024-10-14 14:04:42 +08:00
Kilu.He b5936cec54 fix: supported incremental updates (#6531) 2024-10-11 11:29:29 +08:00
Richard Shiue ea61c81cce feat(flutter_desktop): open created filtered rows (#6522)
* fix: revert previous implementation

* feat: open created but hidden rows as page

* fix: cargo clippy

* test: add integration tests

* fix: typo

* fix: flutter analyzer

* chore: clean up code

* chore: code cleanup

* chore: code cleanup
2024-10-11 11:04:03 +08:00
Kilu.He 64d71d0d20 fix: add sync manager (#6517)
* fix: add sync manager

* fix: add toolbar actions

* fix: add import entry

* fix: modified approve page
2024-10-10 19:48:30 +08:00
Mathias Mogensen af08b4c004 fix: hide breadcrumbs for deleted pages and show trash (#6512)
* fix: hide breadcrumbs for deleted pages and show trash

* fix: copy for banner and trash deletion

* test: add base test

* test: expand on test

* test: fix test

* test: fix failing test
2024-10-10 11:57:42 +02:00
ea670b6ae6 fix: grid new row getting cut (#6475)
* fix: Grid new row getting cut

* chore: add test case for testing text in veiwport

* refac: clean up code

---------

Co-authored-by: Mathias Mogensen <mathiasrieckm@gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2024-10-10 11:02:05 +02:00
Lucas f19e354418 feat: support 'turn into' in doc (#6516)
* feat: customize animation for popover

* chore: code refactor

* feat: using popover direction calculate the popover animation translate direction

* feat: integrate the animated popover in appflowy_popover and popover_action

* fix: close popover assertion

* chore: format code

* chore: code refactor

* feat: optimize the popover listener

* feat: clear popover when hot-reloading

* chore: refactor code

* feat: integrate animated popover in block action button

* fix: integration test

* feat: add turn into entry

* fix: popover asBarrier issue

* feat: move biz logic from widget to cubit

* feat: add turn into menu

* chore: remove unused code

* feat: support h1-h3

* feat: add block conversions

* fix: integration test

* feat: implement block conversions

* fix: outline test

* test: add turn into tests

* test: add turn into nested list test

* test: add turn into nested list test

* fix: flutter analyze

* chore: replace turninto icon

* feat: integrate animated popover in color option button

* chore: split the block option action into separate files

* test: add integration test

* fix: outline block test

* fix: integration test

* fix: shortcut test
2024-10-10 14:40:38 +08:00
Richard Shiue b54e3dd243 feat: checklist cell improvements (#6515)
* feat: reorder checklist tasks

* feat: quickly create checklist tasks

* feat: reorder checklist on mobile

* test: add integration test

* test: add bloc test

* fix: flutter analyzer

* fix: cargo clippy
2024-10-10 11:38:17 +08:00
Richard Shiue 9798e39e49 feat: calendar filters (#6519)
* feat: calendar filters

* test: add integration tests
2024-10-10 11:00:23 +08:00
Richard Shiue 66fd0c19da test: fix flaky sort editor bloc test (#6520) 2024-10-10 11:00:09 +08:00
Mathias Mogensen 8d6c67206c feat: enable drag and drop in row document (#6513)
* feat: enable drag and drop in row document

* refactor: cleanup drop handling to one widget

* fix: clean up code
2024-10-09 15:23:42 +02:00
Lucas 8cf683eb50 feat: customize animation for popover (#6507)
* feat: customize animation for popover

* chore: code refactor

* feat: using popover direction calculate the popover animation translate direction

* feat: integrate the animated popover in appflowy_popover and popover_action

* fix: close popover assertion

* chore: format code

* chore: code refactor

* feat: optimize the popover listener

* feat: clear popover when hot-reloading

* chore: refactor code

* fix: integration test

* fix: icon test
2024-10-09 15:10:05 +08:00
Kilu.He 580a23f3f5 feat: support pasted on web (#6510)
* fix: add some test cases

* fix: with pasted plugin
2024-10-09 11:50:33 +08:00
Richard Shiue 29dc46a993 fix: dont edit every view while handling database view row change (#6506)
* fix: don't process on every view while only handling one view

* test: add test
2024-10-09 11:01:09 +08:00
Richard Shiue 93885bad7b fix: get all calendar events not fetching empty ones (#6508) 2024-10-09 11:00:33 +08:00
Richard Shiue 6e0619c21e chore: bump validator crate version (#6494) 2024-10-08 21:38:58 +08:00
4797 changed files with 176855 additions and 184749 deletions

No files matched your search

+1 -1
View File
@@ -65,7 +65,7 @@ runs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libcurl4-openssl-dev
;;
Windows)
vcpkg integrate install
@@ -52,7 +52,7 @@ runs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager libcurl4-openssl-dev
shell: bash
- name: Enable Flutter Desktop
+178 -108
View File
@@ -1,126 +1,196 @@
# name: Android CI
name: Android CI
# on:
# push:
# branches:
# - "main"
# paths:
# - ".github/workflows/mobile_ci.yaml"
# - "frontend/**"
# - "!frontend/appflowy_tauri/**"
on:
push:
branches:
- "main"
paths:
- ".github/workflows/mobile_ci.yaml"
- "frontend/**"
# pull_request:
# branches:
# - "main"
# paths:
# - ".github/workflows/mobile_ci.yaml"
# - "frontend/**"
# - "!frontend/appflowy_tauri/**"
pull_request:
branches:
- "main"
paths:
- ".github/workflows/mobile_ci.yaml"
- "frontend/**"
- "!frontend/appflowy_tauri/**"
# env:
# CARGO_TERM_COLOR: always
# FLUTTER_VERSION: "3.22.0"
# RUST_TOOLCHAIN: "1.77.2"
# CARGO_MAKE_VERSION: "0.36.6"
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.84.0"
CARGO_MAKE_VERSION: "0.37.18"
CLOUD_VERSION: 0.6.54-amd64
# concurrency:
# group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
# cancel-in-progress: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
# jobs:
# build:
# if: github.event.pull_request.draft != true
# strategy:
# fail-fast: true
# matrix:
# os: [macos-14]
# runs-on: ${{ matrix.os }}
jobs:
build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
# steps:
# - name: Check storage space
# run: df -h
steps:
- name: Check storage space
run:
df -h
# # the following step is required to avoid running out of space
# - name: Maximize build space
# if: matrix.os == 'ubuntu-latest'
# run: |
# sudo rm -rf /usr/share/dotnet
# sudo rm -rf /opt/ghc
# sudo rm -rf "/usr/local/share/boost"
# sudo rm -rf "$AGENT_TOOLSDIRECTORY"
# sudo docker image prune --all --force
# sudo rm -rf /opt/hostedtoolcache/codeQL
# sudo rm -rf ${GITHUB_WORKSPACE}/.git
# sudo rm -rf $ANDROID_HOME/ndk
# the following step is required to avoid running out of space
- name: Maximize build space
if: matrix.os == 'ubuntu-latest'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
# - name: Check storage space
# run: df -h
- name: Check storage space
run: df -h
# - name: Checkout source code
# uses: actions/checkout@v4
- name: Checkout appflowy cloud code
uses: actions/checkout@v4
with:
repository: AppFlowy-IO/AppFlowy-Cloud
path: AppFlowy-Cloud
# - uses: actions/setup-java@v4
# with:
# distribution: temurin
# java-version: 11
- name: Prepare appflowy cloud env
working-directory: AppFlowy-Cloud
run: |
# log level
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's/GOTRUE_EXTERNAL_GOOGLE_ENABLED=.*/GOTRUE_EXTERNAL_GOOGLE_ENABLED=true/' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
# - name: Install Rust toolchain
# id: rust_toolchain
# uses: actions-rs/toolchain@v1
# with:
# toolchain: ${{ env.RUST_TOOLCHAIN }}
# override: true
# profile: minimal
- name: Run Docker-Compose
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
else
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
echo "AppFlowy-Cloud is running with an incorrect version. Restarting with the correct version..."
# Remove all containers if any exist
if [ "$(docker ps -aq)" ]; then
docker rm -f $(docker ps -aq)
else
echo "No containers to remove."
fi
# - name: Install flutter
# id: flutter
# uses: subosito/flutter-action@v2
# with:
# channel: "stable"
# flutter-version: ${{ env.FLUTTER_VERSION }}
# Remove all volumes if any exist
if [ "$(docker volume ls -q)" ]; then
docker volume rm $(docker volume ls -q)
else
echo "No volumes to remove."
fi
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
docker ps -a
docker compose logs
else
echo "AppFlowy-Cloud is running with the correct version."
fi
fi
# - uses: gradle/gradle-build-action@v3
# with:
# gradle-version: 7.4.2
- name: Checkout source code
uses: actions/checkout@v4
# - uses: davidB/rust-cargo-make@v1
# with:
# version: "0.36.6"
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
# - name: Install prerequisites
# working-directory: frontend
# run: |
# rustup target install aarch64-linux-android
# rustup target install x86_64-linux-android
# cargo install --force duckscript_cli
# cargo install cargo-ndk
# if [ "$RUNNER_OS" == "Linux" ]; then
# sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
# sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
# sudo apt-get update
# sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
# sudo apt-get install keybinder-3.0 libnotify-dev
# sudo apt-get install gcc-multilib
# elif [ "$RUNNER_OS" == "Windows" ]; then
# vcpkg integrate install
# elif [ "$RUNNER_OS" == "macOS" ]; then
# echo 'do nothing'
# fi
# cargo make appflowy-flutter-deps-tools
# shell: bash
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
# - name: Build AppFlowy
# working-directory: frontend
# run: |
# cargo make --profile development-android appflowy-android-dev-ci
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
- uses: gradle/gradle-build-action@v3
with:
gradle-version: 8.10
# - name: Run integration tests
# # https://github.com/ReactiveCircus/android-emulator-runner
# uses: reactivecircus/android-emulator-runner@v2
# with:
# api-level: 32
# arch: arm64-v8a
# disk-size: 2048M
# working-directory: frontend/appflowy_flutter
# script: flutter test integration_test/runner.dart
- uses: davidB/rust-cargo-make@v1
with:
version: ${{ env.CARGO_MAKE_VERSION }}
- name: Install prerequisites
working-directory: frontend
run: |
rustup target install aarch64-linux-android
rustup target install x86_64-linux-android
rustup target add armv7-linux-androideabi
cargo install --force --locked duckscript_cli
cargo install cargo-ndk
if [ "$RUNNER_OS" == "Linux" ]; then
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install keybinder-3.0 libnotify-dev
sudo apt-get install gcc-multilib
elif [ "$RUNNER_OS" == "Windows" ]; then
vcpkg integrate install
elif [ "$RUNNER_OS" == "macOS" ]; then
echo 'do nothing'
fi
cargo make appflowy-flutter-deps-tools
shell: bash
- name: Build AppFlowy
working-directory: frontend
run: |
cargo make --profile development-android appflowy-core-dev-android
cargo make --profile development-android code_generation
cd rust-lib
cargo clean
- name: Enable KVM group perms
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
- name: Run integration tests
# https://github.com/ReactiveCircus/android-emulator-runner
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 33
arch: x86_64
disk-size: 2048M
working-directory: frontend/appflowy_flutter
disable-animations: true
force-avd-creation: false
target: google_apis
script: flutter test integration_test/mobile/cloud/cloud_runner.dart
+2 -2
View File
@@ -2,9 +2,9 @@ name: Docker-CI
on:
push:
branches: ["main", "release/*"]
branches: [ "main", "release/*" ]
pull_request:
branches: ["main", "release/*"]
branches: [ "main", "release/*" ]
workflow_dispatch:
concurrency:
+38 -94
View File
@@ -25,9 +25,10 @@ on:
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.22.2"
RUST_TOOLCHAIN: "1.80.1"
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.84.0"
CARGO_MAKE_VERSION: "0.37.18"
CLOUD_VERSION: 0.9.49-amd64
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -39,7 +40,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@@ -73,9 +74,9 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [windows-2019]
os: [ windows-latest ]
include:
- os: windows-2019
- os: windows-latest
flutter_profile: development-windows-x86
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
@@ -100,7 +101,7 @@ jobs:
strategy:
fail-fast: true
matrix:
os: [macos-latest]
os: [ macos-latest ]
include:
- os: macos-latest
flutter_profile: development-mac-x86_64
@@ -122,12 +123,12 @@ jobs:
flutter_profile: ${{ matrix.flutter_profile }}
unit_test:
needs: [prepare-linux]
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@@ -173,7 +174,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libcurl4-openssl-dev
fi
shell: bash
@@ -216,11 +217,11 @@ jobs:
shell: bash
cloud_integration_test:
needs: [prepare-linux]
needs: [ prepare-linux ]
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
@@ -247,9 +248,9 @@ jobs:
- name: Run Docker-Compose
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.29-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
@@ -261,11 +262,26 @@ jobs:
else
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
echo "AppFlowy-Cloud is running with an incorrect version. Restarting with the correct version..."
# Remove all containers if any exist
if [ "$(docker ps -aq)" ]; then
docker rm -f $(docker ps -aq)
else
echo "No containers to remove."
fi
# Remove all volumes if any exist
if [ "$(docker volume ls -q)" ]; then
docker volume rm $(docker volume ls -q)
else
echo "No volumes to remove."
fi
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
docker ps -a
docker compose logs
else
echo "AppFlowy-Cloud is running with the correct version."
fi
@@ -292,7 +308,7 @@ jobs:
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
sudo apt-get update
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev libcurl4-openssl-dev
shell: bash
- name: Enable Flutter Desktop
@@ -323,14 +339,14 @@ jobs:
flutter test integration_test/desktop/cloud/cloud_runner.dart -d Linux --coverage
shell: bash
# split the integration tests into different machines to minimize the time
integration_test_1:
needs: [prepare-linux]
integration_test:
needs: [ prepare-linux ]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ ubuntu-latest ]
test_number: [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
@@ -339,82 +355,10 @@ jobs:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test 1
- name: Flutter Integration Test ${{ matrix.test_number }}
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_1.dart
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}
integration_test_2:
needs: [prepare-linux]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test 2
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_2.dart
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}
integration_test_3:
needs: [prepare-linux]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test 3
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_3.dart
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
rust_target: ${{ matrix.target }}
integration_test_4:
needs: [prepare-linux]
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
include:
- os: ubuntu-latest
target: "x86_64-unknown-linux-gnu"
runs-on: ${{ matrix.os }}
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Flutter Integration Test 4
uses: ./.github/actions/flutter_integration_test
with:
test_path: integration_test/desktop_runner_4.dart
test_path: integration_test/desktop_runner_${{ matrix.test_number }}.dart
flutter_version: ${{ env.FLUTTER_VERSION }}
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
+16 -35
View File
@@ -7,7 +7,6 @@ on:
paths:
- ".github/workflows/mobile_ci.yaml"
- "frontend/**"
- "!frontend/appflowy_tauri/**"
- "!frontend/appflowy_web_app/**"
pull_request:
@@ -16,45 +15,23 @@ on:
paths:
- ".github/workflows/mobile_ci.yaml"
- "frontend/**"
- "!frontend/appflowy_tauri/**"
- "!frontend/appflowy_web_app/**"
env:
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.80.1"
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.84.0"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-self-hosted:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: self-hosted
integration-tests:
runs-on: macos-latest
steps:
- name: Checkout source code
uses: actions/checkout@v2
- name: Build AppFlowy
working-directory: frontend
run: |
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
cargo make --profile development-ios-arm64-sim code_generation
- uses: futureware-tech/simulator-action@v3
id: simulator-action
with:
model: "iPhone 15"
shutdown_after_job: false
build-macos:
if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: macos-13
steps:
- name: Checkout source code
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: actions-rs/toolchain@v1
@@ -85,7 +62,7 @@ jobs:
working-directory: frontend
run: |
rustup target install aarch64-apple-ios-sim
cargo install --force duckscript_cli
cargo install --force --locked duckscript_cli
cargo install cargo-lipo
cargo make appflowy-flutter-deps-tools
shell: bash
@@ -96,11 +73,11 @@ jobs:
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
cargo make --profile development-ios-arm64-sim code_generation
- uses: futureware-tech/simulator-action@v3
id: simulator-action
with:
model: "iPhone 15"
shutdown_after_job: false
# - uses: futureware-tech/simulator-action@v3
# id: simulator-action
# with:
# model: "iPhone 15"
# shutdown_after_job: false
# - name: Run AppFlowy on simulator
# working-directory: frontend/appflowy_flutter
@@ -111,7 +88,11 @@ jobs:
# kill $pid
# continue-on-error: true
# enable it again if the 12 mins timeout is fixed
# # Integration tests
# - name: Run integration tests
# working-directory: frontend/appflowy_flutter
# # The integration tests are flaky and sometimes fail with "Connection timed out":
# # Don't block the CI. If the tests fail, the CI will still pass.
# # Instead, we're using Code Magic to re-run the tests to check if they pass.
# continue-on-error: true
# run: flutter test integration_test/runner.dart -d ${{ steps.simulator-action.outputs.udid }}
+83
View File
@@ -0,0 +1,83 @@
name: Mobile-CI
on:
workflow_dispatch:
inputs:
branch:
description: "Branch to build"
required: true
default: "main"
workflow_id:
description: "Codemagic workflow ID"
required: true
default: "ios-workflow"
type: choice
options:
- ios-workflow
- android-workflow
env:
CODEMAGIC_API_TOKEN: ${{ secrets.CODEMAGIC_API_TOKEN }}
APP_ID: "6731d2f427e7c816080c3674"
jobs:
trigger-mobile-build:
runs-on: ubuntu-latest
steps:
- name: Trigger Codemagic Build
id: trigger_build
run: |
RESPONSE=$(curl -X POST \
--header "Content-Type: application/json" \
--header "x-auth-token: $CODEMAGIC_API_TOKEN" \
--data '{
"appId": "${{ env.APP_ID }}",
"workflowId": "${{ github.event.inputs.workflow_id }}",
"branch": "${{ github.event.inputs.branch }}"
}' \
https://api.codemagic.io/builds)
BUILD_ID=$(echo $RESPONSE | jq -r '.buildId')
echo "build_id=$BUILD_ID" >> $GITHUB_OUTPUT
echo "build_id=$BUILD_ID"
- name: Wait for build and check status
id: check_status
run: |
while true; do
curl -X GET \
--header "Content-Type: application/json" \
--header "x-auth-token: $CODEMAGIC_API_TOKEN" \
https://api.codemagic.io/builds/${{ steps.trigger_build.outputs.build_id }} > /tmp/response.json
RESPONSE_WITHOUT_COMMAND=$(cat /tmp/response.json | jq 'walk(if type == "object" and has("subactions") then .subactions |= map(del(.command)) else . end)')
STATUS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.build.status')
if [ "$STATUS" = "finished" ]; then
SUCCESS=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.success')
BUILD_URL=$(echo $RESPONSE_WITHOUT_COMMAND | jq -r '.buildUrl')
echo "status=$STATUS" >> $GITHUB_OUTPUT
echo "success=$SUCCESS" >> $GITHUB_OUTPUT
echo "build_url=$BUILD_URL" >> $GITHUB_OUTPUT
break
elif [ "$STATUS" = "failed" ]; then
echo "status=failed" >> $GITHUB_OUTPUT
break
fi
sleep 60
done
- name: Slack Notification
uses: 8398a7/action-slack@v3
if: always()
with:
status: ${{ steps.check_status.outputs.success == 'true' && 'success' || 'failure' }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
text: |
Mobile CI Build Result
Branch: ${{ github.event.inputs.branch }}
Workflow: ${{ github.event.inputs.workflow_id }}
Build URL: ${{ steps.check_status.outputs.build_url }}
env:
SLACK_WEBHOOK_URL: ${{ secrets.RELEASE_SLACK_WEBHOOK }}
+23 -22
View File
@@ -6,8 +6,8 @@ on:
- "*"
env:
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.84.0"
jobs:
create-release:
@@ -73,8 +73,8 @@ jobs:
working-directory: frontend
run: |
vcpkg integrate install
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo install --force --locked cargo-make
cargo install --force --locked duckscript_cli
- name: Build Windows app
working-directory: frontend
@@ -135,7 +135,7 @@ jobs:
fail-fast: false
matrix:
job:
- { target: x86_64-apple-darwin, os: macos-12, extra-build-args: "" }
- { target: x86_64-apple-darwin, os: macos-13, extra-build-args: "" }
steps:
- name: Checkout source code
uses: actions/checkout@v4
@@ -158,8 +158,8 @@ jobs:
- name: Install prerequisites
working-directory: frontend
run: |
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo install --force --locked cargo-make
cargo install --force --locked duckscript_cli
- name: Build AppFlowy
working-directory: frontend
@@ -232,10 +232,10 @@ jobs:
matrix:
job:
- {
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
os: macos-latest,
extra-build-args: "",
}
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
os: macos-latest,
extra-build-args: "",
}
steps:
- name: Checkout source code
uses: actions/checkout@v4
@@ -256,8 +256,8 @@ jobs:
- name: Install prerequisites
working-directory: frontend
run: |
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo install --force --locked cargo-make
cargo install --force --locked duckscript_cli
- name: Build AppFlowy
working-directory: frontend
@@ -336,12 +336,12 @@ jobs:
matrix:
job:
- {
arch: x86_64,
target: x86_64-unknown-linux-gnu,
os: ubuntu-20.04,
extra-build-args: "",
flutter_profile: production-linux-x86_64,
}
arch: x86_64,
target: x86_64-unknown-linux-gnu,
os: ubuntu-22.04,
extra-build-args: "",
flutter_profile: production-linux-x86_64,
}
steps:
- name: Checkout source code
uses: actions/checkout@v4
@@ -366,12 +366,13 @@ jobs:
run: |
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
sudo apt-get update
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev
sudo apt-get install -y build-essential libsqlite3-dev libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev libcurl4-openssl-dev
sudo apt-get install keybinder-3.0
sudo apt-get install -y alien libnotify-dev
sudo apt install libfuse2
source $HOME/.cargo/env
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo install --force --locked cargo-make
cargo install --force --locked duckscript_cli
rustup target add ${{ matrix.job.target }}
- name: Install gcc-aarch64-linux-gnu
+30 -92
View File
@@ -15,84 +15,21 @@ on:
- "main"
- "develop"
- "release/*"
paths:
- "frontend/rust-lib/**"
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: "1.77.2"
CLOUD_VERSION: 0.9.49-amd64
RUST_TOOLCHAIN: "1.84.0"
jobs:
self-hosted-job:
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: self-hosted
steps:
- name: Checkout source code
uses: actions/checkout@v4
- name: Checkout Appflowy Cloud
uses: actions/checkout@v4
with:
repository: AppFlowy-IO/AppFlowy-Cloud
path: AppFlowy-Cloud
- name: Prepare Appflowy Cloud env
working-directory: AppFlowy-Cloud
run: |
cp deploy.env .env
sed -i '' 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i '' 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
- name: Ensure AppFlowy-Cloud is Running with Correct Version
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.29-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
else
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
else
echo "AppFlowy-Cloud is running with the correct version."
fi
fi
- name: Run rust-lib tests
working-directory: frontend/rust-lib
env:
RUST_LOG: info
RUST_BACKTRACE: 1
af_cloud_test_base_url: http://localhost
af_cloud_test_ws_url: ws://localhost/ws/v1
af_cloud_test_gotrue_url: http://localhost/gotrue
run: |
DISABLE_CI_TEST_LOG="true" cargo test --no-default-features --features="dart"
- name: rustfmt rust-lib
run: cargo fmt --all -- --check
working-directory: frontend/rust-lib/
- name: clippy rust-lib
run: cargo clippy --all-targets -- -D warnings
working-directory: frontend/rust-lib
ubuntu-job:
if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-latest
steps:
- name: Set timezone for action
uses: szenius/set-timezone@v2.0
with:
timezoneLinux: "US/Pacific"
- name: Maximize build space
run: |
sudo rm -rf /usr/share/dotnet
@@ -129,36 +66,37 @@ jobs:
run: |
cp deploy.env .env
sed -i 's|RUST_LOG=.*|RUST_LOG=trace|' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|GOTRUE_MAILER_AUTOCONFIRM=.*|GOTRUE_MAILER_AUTOCONFIRM=true|' .env
sed -i 's|API_EXTERNAL_URL=.*|API_EXTERNAL_URL=http://localhost|' .env
- name: Ensure AppFlowy-Cloud is Running with Correct Version
working-directory: AppFlowy-Cloud
env:
APPFLOWY_CLOUD_VERSION: 0.6.29-amd64
APPFLOWY_HISTORY_VERSION: 0.6.29-amd64
APPFLOWY_WORKER_VERSION: 0.6.29-amd64
APPFLOWY_CLOUD_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_HISTORY_VERSION: ${{ env.CLOUD_VERSION }}
APPFLOWY_WORKER_VERSION: ${{ env.CLOUD_VERSION }}
run: |
container_id=$(docker ps --filter name=appflowy-cloud-appflowy_cloud-1 -q)
if [ -z "$container_id" ]; then
echo "AppFlowy-Cloud container is not running. Pulling and starting the container..."
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
# Remove all containers if any exist
if [ "$(docker ps -aq)" ]; then
docker rm -f $(docker ps -aq)
else
running_image=$(docker inspect --format='{{index .Config.Image}}' "$container_id")
if [ "$running_image" != "appflowy-cloud:$APPFLOWY_CLOUD_VERSION" ]; then
echo "AppFlowy-Cloud is running with an incorrect version. Pulling the correct version..."
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
else
echo "AppFlowy-Cloud is running with the correct version."
fi
echo "No containers to remove."
fi
# Remove all volumes if any exist
if [ "$(docker volume ls -q)" ]; then
docker volume rm $(docker volume ls -q)
else
echo "No volumes to remove."
fi
docker compose pull
docker compose up -d
echo "Waiting for the container to be ready..."
sleep 10
docker ps -a
docker compose logs
- name: Run rust-lib tests
working-directory: frontend/rust-lib
env:
@@ -168,7 +106,7 @@ jobs:
af_cloud_test_ws_url: ws://localhost/ws/v1
af_cloud_test_gotrue_url: http://localhost/gotrue
run: |
DISABLE_CI_TEST_LOG="true" cargo test --no-default-features --features="dart"
DISABLE_CI_TEST_LOG="true" cargo test --no-default-features --features="dart" -- --skip local_ollama_test
- name: rustfmt rust-lib
run: cargo fmt --all -- --check
+4 -4
View File
@@ -10,8 +10,8 @@ on:
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.22.0"
RUST_TOOLCHAIN: "1.77.2"
FLUTTER_VERSION: "3.27.4"
RUST_TOOLCHAIN: "1.84.0"
jobs:
tests:
@@ -40,8 +40,8 @@ jobs:
- name: Install prerequisites
working-directory: frontend
run: |
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo install --force --locked cargo-make
cargo install --force --locked duckscript_cli
- uses: Swatinem/rust-cache@v2
with:
-124
View File
@@ -1,124 +0,0 @@
name: Tauri-CI
on:
pull_request:
paths:
- ".github/workflows/tauri2_ci.yaml"
- "frontend/rust-lib/**"
- "frontend/appflowy_web_app/**"
- "frontend/resources/**"
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
RUST_TOOLCHAIN: "1.77.2"
CARGO_MAKE_VERSION: "0.36.6"
CI: true
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
# tauri-build-self-hosted:
# if: github.event.pull_request.head.repo.full_name == github.repository
# runs-on: self-hosted
#
# steps:
# - uses: actions/checkout@v4
# - name: install frontend dependencies
# working-directory: frontend/appflowy_web_app
# run: |
# mkdir dist
# pnpm install
# cd src-tauri && cargo build
#
# - name: test and lint
# working-directory: frontend/appflowy_web_app
# run: |
# pnpm run lint:tauri
#
# - uses: tauri-apps/tauri-action@v0
# env:
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# with:
# tauriScript: pnpm tauri
# projectPath: frontend/appflowy_web_app
# args: "--debug"
tauri-build-ubuntu:
#if: github.event.pull_request.head.repo.full_name != github.repository
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- name: Maximize build space
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
sudo rm -rf $ANDROID_HOME/ndk
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
- name: Node_modules cache
uses: actions/cache@v2
with:
path: frontend/appflowy_web_app/node_modules
key: node-modules-${{ runner.os }}
- name: install dependencies
working-directory: frontend
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- uses: taiki-e/install-action@v2
with:
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
- name: install tauri deps tools
working-directory: frontend
run: |
cargo make appflowy-tauri-deps-tools
shell: bash
- name: install frontend dependencies
working-directory: frontend/appflowy_web_app
run: |
mkdir dist
pnpm install
cd src-tauri && cargo build
- name: test and lint
working-directory: frontend/appflowy_web_app
run: |
pnpm run lint:tauri
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tauriScript: pnpm tauri
projectPath: frontend/appflowy_web_app
args: "--debug"
-111
View File
@@ -1,111 +0,0 @@
name: Tauri-CI
on:
push:
branches:
- build/tauri
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
RUST_TOOLCHAIN: "1.77.2"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
tauri-build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
platform: [ ubuntu-20.04 ]
runs-on: ${{ matrix.platform }}
env:
CI: true
steps:
- uses: actions/checkout@v4
- name: Maximize build space (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
sudo rm -rf $ANDROID_HOME/ndk
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./frontend/appflowy_tauri/src-tauri -> target"
- name: Node_modules cache
uses: actions/cache@v2
with:
path: frontend/appflowy_tauri/node_modules
key: node-modules-${{ runner.os }}
- name: install dependencies (windows only)
if: matrix.platform == 'windows-latest'
working-directory: frontend
run: |
cargo install --force duckscript_cli
vcpkg integrate install
- name: install dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
working-directory: frontend
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install cargo-make
working-directory: frontend
run: |
cargo install --force cargo-make
cargo make appflowy-tauri-deps-tools
- name: install frontend dependencies
working-directory: frontend/appflowy_tauri
run: |
mkdir dist
pnpm install
cargo make --cwd .. tauri_build
- name: frontend tests and linting
working-directory: frontend/appflowy_tauri
run: |
pnpm test
pnpm test:errors
- uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tauriScript: pnpm tauri
projectPath: frontend/appflowy_tauri
args: "--debug"
-153
View File
@@ -1,153 +0,0 @@
name: Publish Tauri Release
on:
workflow_dispatch:
inputs:
branch:
description: 'The branch to release'
required: true
default: 'main'
version:
description: 'The version to release'
required: true
default: '0.0.0'
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
RUST_TOOLCHAIN: "1.77.2"
jobs:
publish-tauri:
permissions:
contents: write
strategy:
fail-fast: false
matrix:
settings:
- platform: windows-latest
args: "--verbose"
target: "windows-x86_64"
- platform: macos-latest
args: "--target x86_64-apple-darwin"
target: "macos-x86_64"
- platform: ubuntu-20.04
args: "--target x86_64-unknown-linux-gnu"
target: "linux-x86_64"
runs-on: ${{ matrix.settings.platform }}
env:
CI: true
PACKAGE_PREFIX: AppFlowy_Tauri-${{ github.event.inputs.version }}-${{ matrix.settings.target }}
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.branch }}
- name: Maximize build space (ubuntu only)
if: matrix.settings.platform == 'ubuntu-20.04'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
sudo rm -rf $ANDROID_HOME/ndk
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
override: true
profile: minimal
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: "./frontend/appflowy_tauri/src-tauri -> target"
- name: install dependencies (windows only)
if: matrix.settings.platform == 'windows-latest'
working-directory: frontend
run: |
cargo install --force duckscript_cli
vcpkg integrate install
- name: install dependencies (ubuntu only)
if: matrix.settings.platform == 'ubuntu-20.04'
working-directory: frontend
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: install cargo-make
working-directory: frontend
run: |
cargo install --force cargo-make
cargo make appflowy-tauri-deps-tools
- name: install frontend dependencies
working-directory: frontend/appflowy_tauri
run: |
mkdir dist
pnpm install
pnpm exec node scripts/update_version.cjs ${{ github.event.inputs.version }}
cargo make --cwd .. tauri_build
- uses: tauri-apps/tauri-action@dev
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.MACOS_TEAM_ID }}
APPLE_ID: ${{ secrets.MACOS_NOTARY_USER }}
APPLE_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.MACOS_NOTARY_PWD }}
CI: true
with:
args: ${{ matrix.settings.args }}
appVersion: ${{ github.event.inputs.version }}
tauriScript: pnpm tauri
projectPath: frontend/appflowy_tauri
- name: Upload EXE package(windows only)
uses: actions/upload-artifact@v4
if: matrix.settings.platform == 'windows-latest'
with:
name: ${{ env.PACKAGE_PREFIX }}.exe
path: frontend/appflowy_tauri/src-tauri/target/release/bundle/nsis/AppFlowy_${{ github.event.inputs.version }}_x64-setup.exe
- name: Upload DMG package(macos only)
uses: actions/upload-artifact@v4
if: matrix.settings.platform == 'macos-latest'
with:
name: ${{ env.PACKAGE_PREFIX }}.dmg
path: frontend/appflowy_tauri/src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/AppFlowy_${{ github.event.inputs.version }}_x64.dmg
- name: Upload Deb package(ubuntu only)
uses: actions/upload-artifact@v4
if: matrix.settings.platform == 'ubuntu-20.04'
with:
name: ${{ env.PACKAGE_PREFIX }}.deb
path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/app-flowy_${{ github.event.inputs.version }}_amd64.deb
- name: Upload AppImage package(ubuntu only)
uses: actions/upload-artifact@v4
if: matrix.settings.platform == 'ubuntu-20.04'
with:
name: ${{ env.PACKAGE_PREFIX }}.AppImage
path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/app-flowy_${{ github.event.inputs.version }}_amd64.AppImage
-75
View File
@@ -1,75 +0,0 @@
name: Web-CI
on:
pull_request:
paths:
- ".github/workflows/web2_ci.yaml"
- "frontend/appflowy_web_app/**"
- "frontend/resources/**"
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
web-build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
platform: [ ubuntu-20.04 ]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v4
- name: Maximize build space (ubuntu only)
if: matrix.platform == 'ubuntu-20.04'
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
sudo rm -rf $ANDROID_HOME/ndk
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
- name: Node_modules cache
uses: actions/cache@v2
with:
path: frontend/appflowy_web_app/node_modules
key: node-modules-${{ runner.os }}
- name: install frontend dependencies
working-directory: frontend/appflowy_web_app
run: |
pnpm install
- name: Run lint check
working-directory: frontend/appflowy_web_app
run: |
pnpm run lint
- name: build and analyze
working-directory: frontend/appflowy_web_app
run: |
pnpm run analyze >> analyze-size.txt
- name: Upload analyze-size.txt
uses: actions/upload-artifact@v4
with:
name: analyze-size.txt
path: frontend/appflowy_web_app/analyze-size.txt
retention-days: 30
- name: Upload stats.html
uses: actions/upload-artifact@v4
with:
name: stats.html
path: frontend/appflowy_web_app/dist/stats.html
retention-days: 30
-65
View File
@@ -1,65 +0,0 @@
name: Web Code Coverage
on:
pull_request:
paths:
- ".github/workflows/web2_ci.yaml"
- "frontend/appflowy_web_app/**"
- "frontend/resources/**"
env:
NODE_VERSION: "18.16.0"
PNPM_VERSION: "8.5.0"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
test:
if: github.event.pull_request.draft != true
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Maximize build space (ubuntu only)
run: |
sudo rm -rf /usr/share/dotnet
sudo rm -rf /opt/ghc
sudo rm -rf "/usr/local/share/boost"
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
sudo docker image prune --all --force
sudo rm -rf /opt/hostedtoolcache/codeQL
sudo rm -rf ${GITHUB_WORKSPACE}/.git
sudo rm -rf $ANDROID_HOME/ndk
- name: setup node
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: setup pnpm
uses: pnpm/action-setup@v2
with:
version: ${{ env.PNPM_VERSION }}
# Install pnpm dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v6
with:
working-directory: frontend/appflowy_web_app
component: true
build: pnpm run build
start: pnpm run start
browser: chrome
- name: Jest run
working-directory: frontend/appflowy_web_app
run: |
pnpm run test:unit
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
token: cf9245e0-e136-4e21-b0ee-35755fa0c493
files: frontend/appflowy_web_app/coverage/jest/lcov.info,frontend/appflowy_web_app/coverage/cypress/lcov.info
flags: appflowy_web_app
name: frontend/appflowy_web_app
fail_ci_if_error: true
verbose: true
+272 -2
View File
@@ -1,4 +1,274 @@
# Release Notes
## Version 0.9.1 - 01/05/2025
### Desktop
#### New Features
- Added AppFlowy Prompt Library to AI Chat and Document's Ask AI
- Revamped the desktop in-app notification center
- Supported login with password, as well as forgot and change password options
- Supported copying link to invite members
- Improved the Settings' Members tab with new metadata: member avatar and joined time
#### Bug Fixes
- Fixed data loss when using anonymous local
- Fixed crash when trying to delete an emoji
- Fixed Windows scaling issue
- Correctly displayed mention text by decoding web content
### Mobile
#### New Features
- Supported workspace search
- Improved UX for links in documents
- Supported changing password in Mobile Settings
- Added support for inviting members via links
#### Bug Fixes
- Correctly displayed mention text by decoding web content
## Version 0.9.0 - 30/04/2025
### Desktop
#### New Features
- Added AppFlowy Prompt Library to AI Chat and Document's Ask AI
- Revamped the desktop in-app notification center
- Supported login with password, as well as forgot and change password options
- Supported copying link to invite members
- Improved the Settings' Members tab with new metadata: member avatar and joined time
#### Bug Fixes
- Fixed crash when trying to delete an emoji
- Fixed Windows scaling issue
- Correctly displayed mention text by decoding web content
### Mobile
#### New Features
- Supported workspace search
- Improved UX for links in documents
- Supported changing password in Mobile Settings
- Added support for inviting members via links
#### Bug Fixes
- Correctly displayed mention text by decoding web content
## Version 0.8.9 - 16/04/2025
### Desktop
#### New Features
- Supported pasting a link as a mention, providing a more condensed visualization of linked content
- Supported converting between link formats (e.g. transforming a mention into a bookmark)
- Improved the link editing experience with enhanced UX
- Added OTP (One-Time Password) support for sign-in authentication
- Added latest AI models: GPT-4.1, GPT-4.1-mini, and Claude 3.7 Sonnet
#### Bug Fixes
- Fixed an issue where properties were not displaying in the row detail page
- Fixed a bug where Undo didn't work in the row detail page
- Fixed an issue where blocks didn't grow when the grid got bigger
- Fixed several bugs related to AI writers
### Mobile
#### New Features
- Added sign-in with OTP (One-Time Password)
#### Bug Fixes
- Fixed an issue where the slash menu sometimes failed to display
- Updated the mention page block to handle page selection with more context.
## Version 0.8.8 - 01/04/2025
### New Features
- Added support for selecting AI models in AI writer
- Revamped link menu in toolbar
- Added support for using ":" to add emojis in documents
- Passed the history of past AI prompts and responses to AI writer
### Bug Fixes
- Improved AI writer scrolling user experience
- Fixed issue where checklist items would disappear during reordering
- Fixed numbered lists generated by AI to maintain the same index as the input
## Version 0.8.7 - 18/03/2025
### New Features
- Made local AI free and integrated with Ollama
- Supported nested lists within callout and quote blocks
- Revamped the document's floating toolbar and added Turn Into
- Enabled custom icons in callout blocks
### Bug Fixes
- Fixed occasional incorrect positioning of the slash menu
- Improved AI Chat and AI Writers with various bug fixes
- Adjusted the columns block to match the width of the editor
- Fixed a potential segfault caused by infinite recursion in the trash view
- Resolved an issue where the first added cover might be invisible
- Fixed adding cover images via Unsplash
## Version 0.8.6 - 06/03/2025
### Bug Fixes
- Fix the incorrect title positioning when adjusting the document width setting
- Enhance the user experience of the icon color picker for smoother interactions
- Add missing icons to the database to ensure completeness and consistency
- Resolve the issue with links not functioning correctly on Linux systems
- Improve the outline feature to work seamlessly within columns
- Center the bulleted list icon within columns for better visual alignment
- Enable dragging blocks under tables in the second column to enhance flexibility
- Disable the AI writer feature within tables to prevent conflicts and improve usability
- Automatically enable the header row when converting content from Markdown to ensure proper formatting
- Use the "Undo" function to revert the auto-formatting
## Version 0.8.5 - 04/03/2025
### New Features
- Columns in Documents: Arrange content side by side using drag-and-drop or the slash menu
- AI Writers: New AI assistants in documents with response formatting options (list, table, text with images, image-only), follow-up questions, contextual memory, and more
- Compact Mode for Databases: Enable compact mode for grid and kanban views (full-page and inline) to increase information density, displaying more data per screen
### Bug Fixes
- Fixed an issue where callout blocks couldn’t be deleted when appearing as the first line in a document
- Fixed a bug preventing the relation field in databases from opening
- Fixed an issue where links in documents were unclickable on Linux
## Version 0.8.4 - 18/02/2025
### New Features
- Switch AI mode on mobile
- Support locking page
- Support uploading svg file as icon
- Support the slash, at, and plus menus on mobile
### Bug Fixes
- Gallery not rendering in row page
- Save image should not copy the image (mobile)
- Support exporting more content to markdown
## Version 0.8.2 - 23/01/2025
### New Features
- Customized database view icons
- Support for uploading images as custom icons
- Enabled selecting multiple AI messages to save into a document
- Added the ability to scale the app's display size on mobile
- Support for pasting image links without file extensions
### Bug Fixes
- Fixed an issue where pasting tables from other apps wasn't working
- Fixed homepage URL issues in Settings
- Fixed an issue where the 'Cancel' button was not visible on the Shortcuts page
## Version 0.8.1 - 14/01/2025
### New Features
- AI Chat Layout Options: Customize how AI responses appear with new layouts—List, Table, Image with Text, and Media Only
- DALL-E Integration: Generate stunning AI images from text prompts, now available in AI Chat
- Improved Desktop Search: Find what you need faster using keywords or by asking questions in natural language
- Self-Hosting: Configure web server URLs directly in Settings to enable features like Publish, Copy Link to Share, Custom URLs, and more
- Sidebar Enhancement: Drag to reorder your favorited pages in the Sidebar
- Mobile Table Resizing: Adjust column widths in Simple Tables by long pressing the column borders on mobile
### Bug Fixes
- Resolved an icon rendering issue in callout blocks, tab bars, and search results
- Enhanced image reliability: Retry functionality ensures images load successfully if the first attempt fails
## Version 0.8.0 - 06/01/2025
### Bug Fixes
- Fixed error displaying in the page style menu
- Fixed filter logic in the icon picker
- Fixed error displaying in the Favorite/Recent page
- Fixed the color picker displaying when tapping down
- Fixed icons not being supported in subpage blocks
- Fixed recent icon functionality in the space icon menu
- Fixed "Insert Below" not auto-scrolling the table
- Fixed a to-do item with an emoji automatically creating a soft break
- Fixed header row/column tap areas being too small
- Fixed simple table alignment not working for items that wrap
- Fixed web content reverting after removing the inline code format on desktop
- Fixed inability to make changes to a row or column in the table when opening a new tab
- Fixed changing the language to CKB-KU causing a gray screen on mobile
## Version 0.7.9 - 30/12/2024
### New Features
- Meet AppFlowy Web (Lite): Use AppFlowy directly in your browser.
- Create beautiful documents with 22 content types and markdown support
- Use Quick Note to save anything you want to remember—like meeting notes, a grocery list, or to-dos
- Invite members to your workspace for seamless collaboration
- Create multiple public/private spaces to better organize your content
- Simple Table is now available on Mobile, designed specifically for mobile devices.
- Create and manage Simple Table blocks on Mobile with easy-to-use action menus.
- Use the '+' button in the fixed toolbar to easily add a content block into a table cell on Mobile
- Use '/' to insert a content block into a table cell on Desktop
- Add pages as AI sources in AI chat, enabling you to ask questions about the selected sources
- Add messages to an editable document while chatting with AI side by side
- The new Emoji menu now includes Icons with a Recent section for quickly reusing emojis/icons
- Drag a page from the sidebar into a document to easily mention the page without typing its title
- Paste as plain text, a new option in the right-click paste menu
### Bug Fixes
- Fixed misalignment in numbered lists
- Resolved several bugs in the emoji menu
- Fixed a bug with checklist items
## Version 0.7.8 - 18/12/2024
### New Features
<img width="1068" alt="image" src="https://github.com/user-attachments/assets/cf8bd287-f370-4291-8638-76e2bbf4aaac" />
- Meet Simple Table 2.0:
- Insert a list into a table cell
- Insert images, quotes, callouts, and code blocks into a table cell
- Drag to move rows or columns
- Toggle header rows or columns on/off
- Distribute columns evenly
- Adjust to page width
- Enjoy a new UI/UX for a seamless experience
- Revamped mention page interactions in AI Chat
- Improved AppFlowy AI service
### Bug Fixes
- Fixed an error when opening files in the database in local mode
- Fixed arrow up/down navigation not working for selecting a language in Code Block
- Fixed an issue where deleting multiple blocks using the drag button on the document page didn’t work
## Version 0.7.7 - 09/12/2024
### Bug Fixes
- Fixed sidebar menu resize regression
- Fixed AI chat loading issues
- Fixed inability to open local files in database
- Fixed mentions remaining in notifications after removal from document
- Fixed event card closing when clicking on empty space
- Fixed keyboard shortcut issues
## Version 0.7.6 - 03/12/2024
### New Features
- Revamped the simple table UI
- Added support for capturing images from camera on mobile
### Bug Fixes
- Improved markdown rendering capabilities in AI writer
- Fixed an issue where pressing Enter on a collapsed toggle list would add an unnecessary new line
- Fixed an issue where creating a document from slash menu could insert content at incorrect position
## Version 0.7.5 - 25/11/2024
### Bug Fixes
- Improved chat response parsing
- Fixed toggle list icon direction for RTL mode
- Fixed cross blocks formatting not reflecting in float toolbar
- Fixed unable to click inside the toggle list to create a new paragraph
- Fixed open file error 50 on macOS
- Fixed upload file exceed limit error
## Version 0.7.4 - 19/11/2024
### New Features
- Support uploading WebP and BMP images
- Support managing workspaces on mobile
- Support adding toggle headings on mobile
- Improve the AI chat page UI
### Bug Fixes
- Optimized the workspace menu loading performance
- Optimized tab switching performance
- Fixed searching issues in Document page
## Version 0.7.3 - 07/11/2024
### New Features
- Enable custom URLs for published pages
- Support toggling headings
- Create a subpage by typing in the document
- Turn selected blocks into a subpage
- Add a manual date picker for the Date property
### Bug Fixes
- Fixed an issue where the workspace owner was unable to delete spaces created by others
- Fixed cursor height inconsistencies with text height
- Fixed editing issues in Kanban cards
- Fixed an issue preventing images or files from being dropped into empty paragraphs
## Version 0.7.2 - 22/10/2024
### New Features
- Copy link to block
- Support turn into in document
- Enable sharing links and publishing pages on mobile
- Enable drag and drop in row documents
- Right-click on page in sidebar to open more actions
- Create new subpage in document using `+` character
- Allow reordering checklist item
### Bug Fixes
- Fixed issue with inability to cancel inline code format in French IME
- Fixed delete with Shift or Ctrl shortcuts not working in documents
- Fixed the issues with incorrect time zone being used in filters.
## Version 0.7.1 - 07/10/2024
### New Features
- Copy link to share and open it in a browser
@@ -60,7 +330,7 @@
- Fixed the inability to edit group names on Kanban boards
- Made error codes more user-friendly
- Added leading zeros to day and month in date format
## Version 0.6.8 - 22/08/2024
### New Features
- Enabled viewing data inside a database record on mobile.
@@ -890,4 +1160,4 @@ Bug fixes and improvements
- Increased height of action
- CPU performance issue
- Fix potential data parser error
- More foundation work for online collaboration
- More foundation work for online collaboration
+58 -29
View File
@@ -1,6 +1,6 @@
<h1 align="center" style="border-bottom: none">
<b>
<a href="https://www.appflowy.io">AppFlowy.IO</a><br>
<a href="https://www.appflowy.com">AppFlowy</a><br>
</b>
⭐️ The Open Source Alternative To Notion ⭐️ <br>
</h1>
@@ -18,18 +18,18 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
</p>
<p align="center">
<a href="https://www.appflowy.io"><b>Website</b></a> •
<a href="https://www.appflowy.com"><b>Website</b></a> •
<a href="https://forum.appflowy.io/"><b>Forum</b></a> •
<a href="https://discord.gg/9Q2xaN37tV"><b>Discord</b></a> •
<a href="https://www.reddit.com/r/AppFlowy"><b>Reddit</b></a> •
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
</p>
<p align="center"><img src="https://appflowy.io/_next/static/media/tasks.796c753e.png" alt="AppFlowy Kanban Board for To-dos" /></p>
<p align="center"><img src="https://appflowy.io/_next/static/media/Grid.9e30484b.png" alt="AppFlowy Databases for Tasks and Projects" /></p>
<p align="center"><img src="https://appflowy.io/_next/static/media/sites.a8d5b2b9.png" alt="AppFlowy Sites for Beautiful documentation" /></p>
<p align="center"><img src="https://appflowy.io/_next/static/media/ai.e1460982.png" alt="AppFlowy AI" /></p>
<p align="center"><img src="https://appflowy.io/_next/static/media/template.9ea13c3b.png" alt="AppFlowy Templates" /></p>
<p align="center"><img src="https://appflowy.com/_next/static/media/tasks.796c753e.png" alt="AppFlowy Kanban Board for To-dos" /></p>
<p align="center"><img src="https://appflowy.com/_next/static/media/Grid.9e30484b.png" alt="AppFlowy Databases for Tasks and Projects" /></p>
<p align="center"><img src="https://appflowy.com/_next/static/media/sites.a8d5b2b9.png" alt="AppFlowy Sites for Beautiful documentation" /></p>
<p align="center"><img src="https://appflowy.com/_next/static/media/ai.e1460982.png" alt="AppFlowy AI" /></p>
<p align="center"><img src="https://appflowy.com/_next/static/media/template.9ea13c3b.png" alt="AppFlowy Templates" /></p>
<br></br>
<p align="center" >
@@ -42,11 +42,13 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
## User Installation
- [Download AppFlowy Desktop (macOS, Windows, and Linux)](https://github.com/AppFlowy-IO/AppFlowy/releases)
- Other channels: [FlatHub](https://flathub.org/apps/io.appflowy.AppFlowy), [Snapcraft](https://snapcraft.io/appflowy), [Sourceforge](https://sourceforge.net/projects/appflowy/)
- Other
channels: [FlatHub](https://flathub.org/apps/io.appflowy.AppFlowy), [Snapcraft](https://snapcraft.io/appflowy), [Sourceforge](https://sourceforge.net/projects/appflowy/)
- Available on
- [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone
- [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is not supported
- [Self-hosting AppFlowy](https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy)
- [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone
- [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is
not supported
- [Self-hosting AppFlowy](https://appflowy.com/docs/self-host-appflowy-overview)
- [Source](https://docs.appflowy.io/docs/documentation/appflowy/from-source)
## Built With
@@ -61,32 +63,41 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo
## Getting Started with development
Please view the [documentation](https://docs.appflowy.io/docs/documentation/appflowy/from-source) for OS specific development instructions
Please view the [documentation](https://docs.appflowy.io/docs/documentation/appflowy/from-source) for OS specific
development instructions
## Roadmap
- [AppFlowy Roadmap ReadMe](https://docs.appflowy.io/docs/appflowy/roadmap)
- [AppFlowy Public Roadmap](https://github.com/orgs/AppFlowy-IO/projects/5/views/12)
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+) <br/>
If you'd like to report a bug, submit a bug report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
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+) <br/>
If you'd like to report a bug, submit a bug
report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labels=&template=bug_report.yaml&title=%5BBug%5D+)
## **Releases**
Please see the [changelog](https://www.appflowy.io/whatsnew) for more details about a given release.
Please see the [changelog](https://appflowy.com/what-is-new) for more details about a given release.
## Contributing
Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. Please look at [Contributing to AppFlowy](https://docs.appflowy.io/docs/documentation/software-contributions/contributing-to-appflowy) for details.
Contributions make the open-source community a fantastic place to learn, inspire, and create. Any contributions you make
are **greatly appreciated**. Please look
at [Contributing to AppFlowy](https://docs.appflowy.io/docs/documentation/software-contributions/contributing-to-appflowy)
for details.
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt!
Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter.
If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly
easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains
the community, **Congratulations!** You are now an official contributor to AppFlowy.
## Translations 🌎🗺
[![translation badge](https://inlang.com/badge?url=github.com/AppFlowy-IO/AppFlowy)](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy?ref=badge)
To add translations, you can manually edit the JSON translation files in `/frontend/resources/translations`, use the [inlang online editor](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy), or run `npx inlang machine translate` to add missing translations.
To add translations, you can manually edit the JSON translation files in `/frontend/resources/translations`, use
the [inlang online editor](https://inlang.com/editor/github.com/AppFlowy-IO/AppFlowy), or
run `npx inlang machine translate` to add missing translations.
## Join the community to build AppFlowy together
@@ -96,16 +107,30 @@ To add translations, you can manually edit the JSON translation files in `/front
## Why Are We Building This?
Notion has been our favourite project and knowledge management tool in recent years because of its aesthetic appeal and functionality. Our team uses it daily, and we are on its paid plan. However, as we all know, Notion has its limitations. These include weak data security and poor compatibility with mobile devices. Likewise, alternative collaborative workplace management tools also have their constraints.
Notion has been our favourite project and knowledge management tool in recent years because of its aesthetic appeal and
functionality. Our team uses it daily, and we are on its paid plan. However, as we all know, Notion has its limitations.
These include weak data security and poor compatibility with mobile devices. Likewise, alternative collaborative
workplace management tools also have their constraints.
The limitations we encountered using these tools and our past work experience with collaborative productivity tools have led to our firm belief that there is a glass ceiling on what's possible for these tools in the future. This emanates from the fact that these tools will probably struggle to scale horizontally at some point and be forced to prioritize a proportion of customers whose needs differ from the rest. While decision-makers want a workplace OS, it is impossible to come up with a one-size fits all solution in such a fragmented market.
The limitations we encountered using these tools and our past work experience with collaborative productivity tools have
led to our firm belief that there is a glass ceiling on what's possible for these tools in the future. This emanates
from the fact that these tools will probably struggle to scale horizontally at some point and be forced to prioritize a
proportion of customers whose needs differ from the rest. While decision-makers want a workplace OS, it is impossible to
come up with a one-size fits all solution in such a fragmented market.
When a customer's evolving core needs are not satisfied, they either switch to another or build one from the ground up, in-house. Consequently, they either go under another ceiling or buy an expensive ticket to learn a hard lesson. This is a requirement for many resources and expertise, building a reliable and easy-to-use collaborative tool, not to mention the speed and native experience. The same may apply to individual users as well.
When a customer's evolving core needs are not satisfied, they either switch to another or build one from the ground up,
in-house. Consequently, they either go under another ceiling or buy an expensive ticket to learn a hard lesson. This is
a requirement for many resources and expertise, building a reliable and easy-to-use collaborative tool, not to mention
the speed and native experience. The same may apply to individual users as well.
All these restrictions necessitate our mission - to make it possible for anyone to create apps that suit their needs well.
All these restrictions necessitate our mission - to make it possible for anyone to create apps that suit their needs
well.
- To individuals, we would like to offer Notion's functionality, data security, and cross-platform native experience.
- To enterprises and hackers, AppFlowy is dedicated to offering building blocks and collaboration infra services to enable you to make apps on your own. Moreover, you have 100% control of your data. You can design and modify AppFlowy your way, with a single codebase written in Flutter and Rust supporting multiple platforms armed with long-term maintainability.
- To enterprises and hackers, AppFlowy is dedicated to offering building blocks and collaboration infra services to
enable you to make apps on your own. Moreover, you have 100% control of your data. You can design and modify AppFlowy
your way, with a single codebase written in Flutter and Rust supporting multiple platforms armed with long-term
maintainability.
We decided to achieve this mission by upholding the three most fundamental values:
@@ -113,16 +138,20 @@ We decided to achieve this mission by upholding the three most fundamental value
- Reliable native experience
- Community-driven extensibility
We do not claim to outperform Notion in terms of functionality and design, at least for now. Besides, our priority doesn't lie in more functionality at the moment. Instead, we would like to cultivate a community to democratize the knowledge and wheels of making complex workplace management tools while enabling people and businesses to create beautiful things on their own by equipping them with a versatile toolbox of building blocks.
We do not claim to outperform Notion in terms of functionality and design, at least for now. Besides, our priority
doesn't lie in more functionality at the moment. Instead, we would like to cultivate a community to democratize the
knowledge and wheels of making complex workplace management tools while enabling people and businesses to create
beautiful things on their own by equipping them with a versatile toolbox of building blocks.
## License
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for more information.
Distributed under the AGPLv3 License. See [`LICENSE.md`](https://github.com/AppFlowy-IO/AppFlowy/blob/main/LICENSE) for
more information.
## Acknowledgements
## Acknowledgments
Special thanks to these amazing projects which help power AppFlowy.IO:
Special thanks to these amazing projects which help power AppFlowy:
- [flutter-quill](https://github.com/singerdmx/flutter-quill)
- [cargo-make](https://github.com/sagiegurari/cargo-make)
- [contrib.rocks](https://contrib.rocks)
- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui)
+47
View File
@@ -0,0 +1,47 @@
workflows:
ios-workflow:
name: iOS Workflow
instance_type: mac_mini_m2
max_build_duration: 30
environment:
flutter: 3.27.4
xcode: latest
cocoapods: default
scripts:
- name: Build Flutter
script: |
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
source "$HOME/.cargo/env"
rustc --version
cargo --version
cd frontend
rustup target install aarch64-apple-ios-sim
cargo install --force cargo-make
cargo install --force --locked duckscript_cli
cargo install --force cargo-lipo
cargo make appflowy-flutter-deps-tools
cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios
cargo make --profile development-ios-arm64-sim code_generation
- name: iOS integration tests
script: |
cd frontend/appflowy_flutter
flutter emulators --launch apple_ios_simulator
flutter -d iPhone test integration_test/runner.dart
artifacts:
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
- flutter_drive.log
publishing:
email:
recipients:
- lucas.xu@appflowy.io
notify:
success: true
failure: true
+123 -138
View File
@@ -1,140 +1,125 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// This task only builds the Dart code of AppFlowy.
// It supports both the desktop and mobile version.
"name": "AF: Build Dart Only",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"env": {
"RUST_LOG": "debug",
},
// uncomment the following line to testing performance.
// "flutterMode": "profile",
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// This task builds the Rust and Dart code of AppFlowy.
"name": "AF-desktop: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core",
"env": {
"RUST_LOG": "trace",
"RUST_BACKTRACE": "1"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// This task builds will:
// - call the clean task,
// - rebuild all the generated Files (including freeze and language files)
// - rebuild the the Rust and Dart code of AppFlowy.
"name": "AF-desktop: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For iOS",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS-Simulator: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For iOS Simulator",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS-Simulator: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS Simulator)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-Android: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For Android",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-Android: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (Android)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-desktop: Debug Rust",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}"
// To launch the application directly, use the following configuration:
// "request": "launch",
// "program": "[YOUR_APPLICATION_PATH]",
},
{
// https://tauri.app/v1/guides/debugging/vs-code
"type": "lldb",
"request": "launch",
"name": "AF-tauri: Debug backend",
"cargo": {
"args": [
"build",
"--manifest-path=./appflowy_tauri/src-tauri/Cargo.toml",
"--no-default-features"
]
},
"preLaunchTask": "AF: Tauri UI Dev",
"cwd": "${workspaceRoot}/appflowy_tauri/"
},
]
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// This task only builds the Dart code of AppFlowy.
// It supports both the desktop and mobile version.
"name": "AF: Build Dart Only",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"env": {
"RUST_LOG": "debug",
},
// uncomment the following line to testing performance.
// "flutterMode": "profile",
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// This task builds the Rust and Dart code of AppFlowy.
"name": "AF-desktop: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core",
"env": {
"RUST_LOG": "trace",
"RUST_BACKTRACE": "1"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// This task builds will:
// - call the clean task,
// - rebuild all the generated Files (including freeze and language files)
// - rebuild the the Rust and Dart code of AppFlowy.
"name": "AF-desktop: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For iOS",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS-Simulator: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For iOS Simulator",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-iOS-Simulator: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (iOS Simulator)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-Android: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Build Appflowy Core For Android",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-Android: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (Android)",
"env": {
"RUST_LOG": "trace"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
"name": "AF-desktop: Debug Rust",
"type": "lldb",
"request": "attach",
"pid": "${command:pickMyProcess}"
// To launch the application directly, use the following configuration:
// "request": "launch",
// "program": "[YOUR_APPLICATION_PATH]",
},
]
}
-45
View File
@@ -245,51 +245,6 @@
"problemMatcher": [],
"detail": "appflowy_flutter"
},
{
"label": "AF: Tauri UI Build",
"type": "shell",
"command": "pnpm run build",
"options": {
"cwd": "${workspaceFolder}/appflowy_tauri"
}
},
{
"label": "AF: Tauri UI Dev",
"type": "shell",
"isBackground": true,
"command": "pnpm sync:i18n && pnpm run dev",
"options": {
"cwd": "${workspaceFolder}/appflowy_tauri"
}
},
{
"label": "AF: Tauri Clean",
"type": "shell",
"command": "cargo make tauri_clean",
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "AF: Tauri Clean + Dev",
"type": "shell",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Tauri Clean",
"AF: Tauri UI Dev"
],
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "AF: Tauri ESLint",
"type": "shell",
"command": "npx eslint --fix src",
"options": {
"cwd": "${workspaceFolder}/appflowy_tauri"
}
},
{
"label": "AF: Generate Env File",
"type": "shell",
+36 -35
View File
@@ -1,16 +1,16 @@
#https://github.com/sagiegurari/cargo-make
extend = [
{ path = "scripts/makefile/desktop.toml" },
{ path = "scripts/makefile/mobile.toml" },
{ path = "scripts/makefile/protobuf.toml" },
{ path = "scripts/makefile/tests.toml" },
{ path = "scripts/makefile/docker.toml" },
{ path = "scripts/makefile/env.toml" },
{ path = "scripts/makefile/flutter.toml" },
{ path = "scripts/makefile/tool.toml" },
{ path = "scripts/makefile/tauri.toml" },
{ path = "scripts/makefile/web.toml" },
{ path = "scripts/makefile/desktop.toml" },
{ path = "scripts/makefile/mobile.toml" },
{ path = "scripts/makefile/protobuf.toml" },
{ path = "scripts/makefile/tests.toml" },
{ path = "scripts/makefile/docker.toml" },
{ path = "scripts/makefile/env.toml" },
{ path = "scripts/makefile/flutter.toml" },
{ path = "scripts/makefile/tool.toml" },
{ path = "scripts/makefile/tauri.toml" },
{ path = "scripts/makefile/web.toml" },
]
[config]
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.7.1"
APPFLOWY_VERSION = "0.9.1"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
@@ -228,30 +228,31 @@ script = ['''
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
echo BUILD_ARCHS: ${BUILD_ARCHS}
echo BUILD_VERSION: ${BUILD_VERSION}
echo RUST_VERSION: "$(rustc --version)"
''']
script_runner = "@shell"
[tasks.setup-crate-type]
private = true
script = [
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} "staticlib" ${CRATE_TYPE}
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} "staticlib" ${CRATE_TYPE}
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
]
script_runner = "@duckscript"
[tasks.restore-crate-type]
private = true
script = [
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} ${CRATE_TYPE} "staticlib"
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} ${CRATE_TYPE} "staticlib"
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
]
script_runner = "@duckscript"
@@ -279,24 +280,24 @@ TEST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
[tasks.setup-test-crate-type]
private = true
script = [
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} "staticlib" ${TEST_CRATE_TYPE}
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} "staticlib" ${TEST_CRATE_TYPE}
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
]
script_runner = "@duckscript"
[tasks.restore-test-crate-type]
private = true
script = [
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} ${TEST_CRATE_TYPE} "staticlib"
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
"""
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
val = replace ${toml} ${TEST_CRATE_TYPE} "staticlib"
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
assert ${result}
""",
]
script_runner = "@duckscript"
@@ -1,32 +1,12 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- "packages/**/*.dart"
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
- require_trailing_commas
@@ -51,8 +31,5 @@ linter:
- sort_constructors_first
- unawaited_futures
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
errors:
invalid_annotation_target: ignore
@@ -53,7 +53,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.appflowy.appflowy"
minSdkVersion 29
targetSdkVersion 34
targetSdkVersion 35
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
@@ -36,7 +36,6 @@
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="appflowy-flutter" />
<!-- <data android:host="login-callback" /> -->
</intent-filter>
</activity>
<!--
@@ -44,13 +43,16 @@
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
-->
<meta-data android:name="flutterEmbedding" android:value="2" />
<meta-data android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Permission to read files from external storage (outside application container).
As of Android 12 this permission no longer has any effect. Instead use the
READ_MEDIA_IMAGES, READ_MEDIA_VIDEO or READM_MEDIA_AUDIO permissions. -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Permissions to read media files. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<queries>
@@ -65,4 +67,5 @@
-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.CAMERA" />
</manifest>
File diff suppressed because it is too large. Load diff
Whitespace-only changes.
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8ZM9.25 3.75C9.25 4.44036 8.69036 5 8 5C7.30964 5 6.75 4.44036 6.75 3.75C6.75 3.05964 7.30964 2.5 8 2.5C8.69036 2.5 9.25 3.05964 9.25 3.75ZM12 8H9.41901L11.2047 13H9.081L8 9.97321L6.91901 13H4.79528L6.581 8H4V6H12V8Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 617 B

@@ -8,7 +8,7 @@
"{""data"":""Juan"",""last_modified"":1726063423,""created_at"":1726063423,""field_type"":0}","{""created_at"":1726122510,""reminder_id"":"""",""include_time"":false,""is_range"":true,""last_modified"":1726122515,""data"":""1725604115"",""end_timestamp"":""1725776915"",""field_type"":2}","{""field_type"":1,""created_at"":1726063677,""last_modified"":1726063677,""data"":""93100""}","{""field_type"":1,""data"":""4.86"",""created_at"":1726062597,""last_modified"":1726062597}",,"{""last_modified"":1726062377,""field_type"":5,""data"":""Yes"",""created_at"":1726062377}","{""last_modified"":1726064412,""field_type"":7,""data"":""{\""options\"":[{\""id\"":\""tTDq\"",\""name\"":\""complete onboarding\"",\""color\"":\""Purple\""},{\""id\"":\""E8Ds\"",\""name\"":\""contact support\"",\""color\"":\""Purple\""},{\""id\"":\""RoGN\"",\""name\"":\""get started\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""tTDq\"",\""E8Ds\""]}"",""created_at"":1726064396}",,"{""created_at"":1726065278,""field_type"":3,""data"":""qnja"",""last_modified"":1726065278}","{""data"":""R9I7,yORP,1i4f"",""field_type"":4,""created_at"":1726105126,""last_modified"":1726105127}","{""data"":""1726122515"",""field_type"":8}","{""data"":""1726060541"",""field_type"":9}"
"{""data"":""Alex"",""created_at"":1726063432,""last_modified"":1726063432,""field_type"":0}","{""reminder_id"":"""",""data"":""1725292800"",""include_time"":true,""last_modified"":1726122448,""created_at"":1726122422,""is_range"":true,""end_timestamp"":""1725551940"",""field_type"":2}","{""field_type"":1,""last_modified"":1726063683,""created_at"":1726063683,""data"":""3560""}","{""created_at"":1726062561,""data"":""1.96"",""last_modified"":1726062561,""field_type"":1}","{""last_modified"":1726063952,""created_at"":1726063931,""data"":""al3x1343@protonmail.com"",""field_type"":6}","{""last_modified"":1726062375,""field_type"":5,""created_at"":1726062375,""data"":""Yes""}","{""data"":""{\""options\"":[{\""id\"":\""qNyr\"",\""name\"":\""finish reading book\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[]}"",""created_at"":1726064616,""last_modified"":1726064616,""field_type"":7}",,"{""data"":""qnja"",""created_at"":1726065272,""last_modified"":1726065272,""field_type"":3}","{""created_at"":1726105180,""last_modified"":1726105180,""field_type"":4,""data"":""R9I7,1i4f""}","{""field_type"":8,""data"":""1726122448""}","{""field_type"":9,""data"":""1726060541""}"
"{""last_modified"":1726063478,""created_at"":1726063436,""field_type"":0,""data"":""Alexander""}",,"{""field_type"":1,""last_modified"":1726063691,""created_at"":1726063691,""data"":""2073""}","{""field_type"":1,""data"":""0.5"",""last_modified"":1726062577,""created_at"":1726062577}","{""last_modified"":1726063991,""field_type"":6,""created_at"":1726063991,""data"":""alexandernotthedra@gmail.com""}","{""field_type"":5,""last_modified"":1726062378,""created_at"":1726062377,""data"":""No""}",,,"{""created_at"":1726065291,""data"":""GSf_"",""last_modified"":1726065291,""field_type"":3}","{""last_modified"":1726105142,""created_at"":1726105133,""data"":""SEUo"",""field_type"":4}","{""field_type"":8,""data"":""1726105142""}","{""field_type"":9,""data"":""1726060542""}"
"{""field_type"":0,""created_at"":1726063454,""last_modified"":1726063454,""data"":""George""}","{""created_at"":1726122467,""end_timestamp"":""1726468070"",""include_time"":false,""is_range"":true,""reminder_id"":"""",""field_type"":2,""data"":""1726295270"",""last_modified"":1726122470}",,,"{""field_type"":6,""data"":""george.aq@appflowy.io"",""last_modified"":1726064104,""created_at"":1726064016}","{""last_modified"":1726062376,""created_at"":1726062376,""field_type"":5,""data"":""Yes""}","{""data"":""{\""options\"":[{\""id\"":\""s_dQ\"",\""name\"":\""bug triage\"",\""color\"":\""Purple\""},{\""id\"":\""-Zfo\"",\""name\"":\""fix bugs\"",\""color\"":\""Purple\""},{\""id\"":\""wsDN\"",\""name\"":\""attend meetings\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""s_dQ\"",\""-Zfo\""]}"",""last_modified"":1726064468,""created_at"":1726064424,""field_type"":7}","{""data"":[""{\""id\"":\""8a77f84d-64e9-4e67-b902-fa23980459ec\"",\""name\"":\""BQdTmxpRI6f.png\"",\""url\"":\""https://static.cdninstagram.com/rsrc.php/v3/ym/r/BQdTmxpRI6f.png\"",\""upload_type\"":\""NetworkMedia\"",\""file_type\"":\""Image\""}""],""field_type"":14,""created_at"":1726122956,""last_modified"":1726122956}","{""field_type"":3,""data"":""qnja"",""created_at"":1726065313,""last_modified"":1726065313}","{""data"":""R9I7,yORP"",""field_type"":4,""last_modified"":1726105198,""created_at"":1726105187}","{""data"":""1726122956"",""field_type"":8}","{""data"":""1726060543"",""field_type"":9}"
"{""field_type"":0,""created_at"":1726063454,""last_modified"":1726063454,""data"":""George""}","{""created_at"":1726122467,""end_timestamp"":""1726468070"",""include_time"":false,""is_range"":true,""reminder_id"":"""",""field_type"":2,""data"":""1726295270"",""last_modified"":1726122470}",,,"{""field_type"":6,""data"":""george.aq@appflowy.io"",""last_modified"":1726064104,""created_at"":1726064016}","{""last_modified"":1726062376,""created_at"":1726062376,""field_type"":5,""data"":""Yes""}","{""data"":""{\""options\"":[{\""id\"":\""s_dQ\"",\""name\"":\""bug triage\"",\""color\"":\""Purple\""},{\""id\"":\""-Zfo\"",\""name\"":\""fix bugs\"",\""color\"":\""Purple\""},{\""id\"":\""wsDN\"",\""name\"":\""attend meetings\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""s_dQ\"",\""-Zfo\""]}"",""last_modified"":1726064468,""created_at"":1726064424,""field_type"":7}","{""data"":[""{\""id\"":\""8a77f84d-64e9-4e67-b902-fa23980459ec\"",\""name\"":\""BQdTmxpRI6f.png\"",\""url\"":\""https://file-examples.com/wp-content/storage/2017/10/file_example_PNG_500kB.png\"",\""upload_type\"":\""NetworkMedia\"",\""file_type\"":\""Image\""}""],""field_type"":14,""created_at"":1726122956,""last_modified"":1726122956}","{""field_type"":3,""data"":""qnja"",""created_at"":1726065313,""last_modified"":1726065313}","{""data"":""R9I7,yORP"",""field_type"":4,""last_modified"":1726105198,""created_at"":1726105187}","{""data"":""1726122956"",""field_type"":8}","{""data"":""1726060543"",""field_type"":9}"
"{""field_type"":0,""last_modified"":1726063467,""data"":""Joanna"",""created_at"":1726063467}","{""include_time"":false,""end_timestamp"":""1727072893"",""is_range"":true,""last_modified"":1726122493,""created_at"":1726122483,""data"":""1726554493"",""field_type"":2,""reminder_id"":""""}","{""last_modified"":1726065463,""data"":""16470"",""field_type"":1,""created_at"":1726065463}","{""created_at"":1726062626,""field_type"":1,""last_modified"":1726062626,""data"":""-5.36""}","{""last_modified"":1726064069,""data"":""joannastrawberry29+hello@gmail.com"",""created_at"":1726064069,""field_type"":6}",,"{""field_type"":7,""created_at"":1726064444,""last_modified"":1726064460,""data"":""{\""options\"":[{\""id\"":\""ZxJz\"",\""name\"":\""post on Twitter\"",\""color\"":\""Purple\""},{\""id\"":\""upwi\"",\""name\"":\""watch Youtube videos\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""upwi\""]}""}",,"{""created_at"":1726065317,""last_modified"":1726065317,""field_type"":3,""data"":""qnja""}","{""field_type"":4,""last_modified"":1726105173,""data"":""uRAO,yORP"",""created_at"":1726105170}","{""data"":""1726122493"",""field_type"":8}","{""data"":""1726060545"",""field_type"":9}"
"{""last_modified"":1726063457,""created_at"":1726063457,""data"":""George"",""field_type"":0}","{""include_time"":true,""reminder_id"":"""",""field_type"":2,""is_range"":true,""created_at"":1726122521,""end_timestamp"":""1725829200"",""data"":""1725822900"",""last_modified"":1726122535}","{""last_modified"":1726065493,""field_type"":1,""data"":""9500"",""created_at"":1726065493}","{""last_modified"":1726062680,""created_at"":1726062680,""field_type"":1,""data"":""1.7""}","{""data"":""plgeorgebball@gmail.com"",""field_type"":6,""last_modified"":1726064087,""created_at"":1726064036}",,"{""last_modified"":1726064513,""data"":""{\""options\"":[{\""id\"":\""zy0x\"",\""name\"":\""game vs celtics\"",\""color\"":\""Purple\""},{\""id\"":\""WJsv\"",\""name\"":\""training\"",\""color\"":\""Purple\""},{\""id\"":\""w-f8\"",\""name\"":\""game vs spurs\"",\""color\"":\""Purple\""},{\""id\"":\""p1VQ\"",\""name\"":\""game vs knicks\"",\""color\"":\""Purple\""},{\""id\"":\""VjUA\"",\""name\"":\""recovery\"",\""color\"":\""Purple\""},{\""id\"":\""sQ8X\"",\""name\"":\""don't get injured\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[]}"",""created_at"":1726064486,""field_type"":7}",,"{""field_type"":3,""last_modified"":1726065310,""data"":""qnja"",""created_at"":1726065310}","{""created_at"":1726105205,""field_type"":4,""last_modified"":1726105249,""data"":""R9I7,1i4f,yORP,SEUo""}","{""data"":""1726122535"",""field_type"":8}","{""field_type"":9,""data"":""1726060546""}"
"{""data"":""Judy"",""created_at"":1726063475,""field_type"":0,""last_modified"":1726063487}","{""end_timestamp"":"""",""reminder_id"":"""",""data"":""1726640950"",""field_type"":2,""include_time"":false,""created_at"":1726122550,""last_modified"":1726122550,""is_range"":false}",,,"{""created_at"":1726063882,""field_type"":6,""last_modified"":1726064000,""data"":""judysmithjr@outlook.com""}","{""last_modified"":1726062712,""field_type"":5,""data"":""Yes"",""created_at"":1726062712}","{""created_at"":1726064549,""field_type"":7,""data"":""{\""options\"":[{\""id\"":\""j8cC\"",\""name\"":\""finish training\"",\""color\"":\""Purple\""},{\""id\"":\""SmSk\"",\""name\"":\""brainwash\"",\""color\"":\""Purple\""},{\""id\"":\""mnf5\"",\""name\"":\""welcome to ba sing se\"",\""color\"":\""Purple\""},{\""id\"":\""hcrj\"",\""name\"":\""don't mess up\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""j8cC\"",\""SmSk\"",\""mnf5\"",\""hcrj\""]}"",""last_modified"":1726064591}",,,"{""field_type"":4,""last_modified"":1726105152,""created_at"":1726105152,""data"":""R9I7""}","{""field_type"":8,""data"":""1726122550""}","{""field_type"":9,""data"":""1726060549""}"
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,12 @@
output: dist/
releases:
- name: dev
jobs:
- name: release-dev-linux-deb
package:
platform: linux
target: deb
- name: release-dev-linux-rpm
package:
platform: linux
target: rpm
+36
View File
@@ -0,0 +1,36 @@
-----BEGIN PUBLIC KEY-----
MIIGQzCCBDUGByqGSM44BAEwggQoAoICAQDlkozRmUnVH1MJFqOamAmUYu0YruaT
rrt6rCIZ0LFrfNnmHA4LOQEcXwBTTyn5sBmkPq+lb/rjmERKhmvl1rfo6q7tJ8mG
4TWqSu0tOJQ6QxexnNW4yhzK/r9MS5MQus4Al+y2hQLaAMOUIOnaWIrC9OHy7xyw
+sVipECVKyQqipS4shGUSqbcN+ocQuTB+I0MtIjBii0DGSEY3pxQrfNWjHFL7iTV
KiTn3YOWPJQvv3FvEDrN+5xU5JZpD97ZhXaJpLUyOQaNvcPaOELPWcOSJwqHOpf5
b5N/VZ8SGbHNdxy9d5sSChBgtuAOihEhSp6SjFQ9eVHOf4NyJwSEMmi0gpdpqm4Z
QRJUnM2zIi0p9twR9FRYXzrxOs6yGCQEY+xFG93ShTLTj3zMrIyFqBsqEwFyJiJW
YWe/zp0V7UlLP+jXO9u9eghNmly7QVqD2P0qs/1V0jZFRuLWpsv4inau/qMZ5EhG
G4xCJZXfN1pkehy6e05/h+vs5anK3Wa/H8AtY6cK4CpzAanELvn3AH7VLbAhLswu
6d5CV+DoFgxCWMzGBSdmCYU+2wRLaL8Q9TZHDR+pvQlunEFdfFoGES9WjBPhAsVA
6Mq22U8XSje9yHI3X9Eqe/7a+ajSgcGmB7oQ11+4xf5h2PtubRW/JL0KMjxCxMTp
q1md6Ndx/ptBUwIdAIOyiKb2YcTLWAOt+LAlRXMsY1+W4pTXJfV6RcMCggIAPxbd
0HNj2O/aQhJxNZDMBIcx6+cZ+LKch7qLcaEpVqWHvDSnR2eOJJzWn0RoKK+Vuix/
4T8texSQkWxAeFFdo6kyrR9XNL7hqEFFq8o9VpmvRzvG6h/bBgh3AHAQE3p/8Wrb
K13IhnlWqd0MjFufSphm63o0gaWl95j+6KeUoKQnioetu9HiMtFKx0d/KYqTQJg7
hvR6VNCU2oShfXR3ce7RnUYwD37+djrUjUkoAZkZq2KoxBiKyeoSIeqAme19tKcO
s6b17mhALELuJ+NtDwlDunyiCDUYX9lTPijHwKeIFtBs38+OtRk3aIqmWTQdbsCz
Axp+kUMA5ESBME/RBNCSPHuDvtA3wfWvNbA5DXfZLwCgNSxhekq8XntIsRzfJ4v4
uPzKFcVM3+sUUfSF04HHC9ol+PpLqXUyMnskiizqxFPq7H+6tyFZ7X2HiG6TjcfV
Wthmv+JyfcABjVnk2qFH7GagENbdtYmfUox13LhE59Sh5chaJnCFtCDp8NClWgZn
ixCOFQ9EgTLaH6MovTvWpEgG2MfBCu5SMUHi2qSflorqpRFH+rA7NZSnyz3wm7NB
+fJSOP0IjEkOh7MafU6Z61oK9WY/Fc+F1zIENVv8PUc3p75y/4RAp4xzyKcTilaN
C9U/3MRr3QmWwY7ejtZx6xdOxsvWBRDRSNbDdIkDggIGAAKCAgEAt1DHYZoeXY0r
vYXmxdNO6zfnbz1GGZHXpakzm9h4BrxPDP5J8DQ9ZeVVKg5+cU9AyMO3cZHp7wkx
k6IB+ZDUpqO1D3lWriRl2fI8cS4edI0fzpnW1nyhhFD4MbKmP+v27aH+DhZ4Up3y
GMmJTLmKiYx1EgZp7Sx77PBYDVMsKKd3h9+Hjp2YtUTfD2lleAmC+wcQGZiNtGw/
eKpsmUVnWrepOdntWTtCQi1OvfcHaF2QmgktCq+68hbDNYWaXmzVIiQqrdv/zzOG
hCFIrRGWemrxL0iFG4Pzc4UfOINsISQLcUxRuF6pQWPxF8O/mWKfzAeqWxmIujUM
EoSEuI3yQ8VjlYpW/8FSK7UhgnHBHOpCJWPWs/vQXAnaUR2PYyzuIzhVEhFs8YA8
iBIKnixIC2hu0YbEk3TBr/TRcbd7mDw9Mq7NT88xzdU13+Wh+4zhdX3rtBHYzBtI
7GaONGUNyY4h0duoyLpH6dxevaeKN6/bEdzYESjoE58QA88CpnAZGhJVphAba4cb
w6GTDhK3RlPWh6hRqJwLDILGtnJS3UKeBDRmKMqNuqmHqPjyAAvt9JBO8lzjoLgf
1cDsXHNWBVwA2jsX2CukNJPlY1Fa3MWhdaUXmy6QGMSisr1sptvBt1Phry8T2u+P
Y29SB4jvwqls268rP0cWqy4WXwlVwuc=
-----END PUBLIC KEY-----
@@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database/board/presentation/widgets/board_colum
import 'package:appflowy/plugins/database/board/presentation/widgets/board_hidden_groups.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -24,24 +23,24 @@ void main() {
final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s);
// Is expanded by default
expect(collapseFinder, findsOneWidget);
expect(expandFinder, findsNothing);
// Collapse hidden groups
await tester.tap(collapseFinder);
await tester.pumpAndSettle();
// Is collapsed
expect(collapseFinder, findsNothing);
expect(expandFinder, findsOneWidget);
// Expand hidden groups
// Collapse hidden groups
await tester.tap(expandFinder);
await tester.pumpAndSettle();
// Is expanded
// Is collapsed
expect(collapseFinder, findsOneWidget);
expect(expandFinder, findsNothing);
// Expand hidden groups
await tester.tap(collapseFinder);
await tester.pumpAndSettle();
// Is expanded
expect(collapseFinder, findsNothing);
expect(expandFinder, findsOneWidget);
});
testWidgets('hide first group, and show it again', (tester) async {
@@ -49,6 +48,9 @@ void main() {
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s);
await tester.tapButton(expandFinder);
// Tap the options of the first group
final optionsFinder = find
.descendant(
@@ -121,22 +123,3 @@ void main() {
});
});
}
extension FlowySvgFinder on CommonFinders {
Finder byFlowySvg(FlowySvgData svg) => _FlowySvgFinder(svg);
}
class _FlowySvgFinder extends MatchFinder {
_FlowySvgFinder(this.svg);
final FlowySvgData svg;
@override
String get description => 'flowy_svg "$svg"';
@override
bool matches(Element candidate) {
final Widget widget = candidate.widget;
return widget is FlowySvg && widget.svg == svg;
}
}
@@ -6,6 +6,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:time/time.dart';
import '../../shared/database_test_op.dart';
import '../../shared/util.dart';
@@ -14,6 +15,31 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('board row test', () {
testWidgets('edit item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board);
const name = 'Card 1';
final card1 = find.ancestor(
matching: find.byType(RowCard),
of: find.text(name),
);
await tester.hoverOnWidget(
card1,
onHover: () async {
final editCard = find.byType(EditCardAccessory);
await tester.tapButton(editCard);
},
);
await tester.showKeyboard(card1);
tester.testTextInput.enterText("");
await tester.pump(300.milliseconds);
tester.testTextInput.enterText("a");
await tester.pump(300.milliseconds);
expect(find.text('a'), findsOneWidget);
});
testWidgets('delete item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -1,25 +1,49 @@
import 'data_migration/data_migration_test_runner.dart'
as data_migration_test_runner;
import 'database/database_test_runner.dart' as database_test_runner;
import 'document/document_test_runner.dart' as document_test_runner;
import 'set_env.dart' as preset_af_cloud_env_test;
import 'sidebar/sidebar_icon_test.dart' as sidebar_icon_test;
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
import 'sidebar/sidebar_rename_untitled_test.dart'
as sidebar_rename_untitled_test;
import 'uncategorized/uncategorized_test_runner.dart'
as uncategorized_test_runner;
import 'workspace/workspace_test_runner.dart' as workspace_test_runner;
import 'data_migration/data_migration_test_runner.dart'
as data_migration_test_runner;
import 'set_env.dart' as preset_af_cloud_env_test;
Future<void> main() async {
preset_af_cloud_env_test.main();
// don't remove this test, it can prevent the test from failing.
{
preset_af_cloud_env_test.main();
data_migration_test_runner.main();
data_migration_test_runner.main();
// uncategorized
uncategorized_test_runner.main();
// uncategorized
uncategorized_test_runner.main();
// workspace
workspace_test_runner.main();
// document
document_test_runner.main();
// workspace
workspace_test_runner.main();
}
// sidebar
sidebar_move_page_test.main();
// don't remove this test, it can prevent the test from failing.
{
preset_af_cloud_env_test.main();
sidebar_move_page_test.main();
sidebar_rename_untitled_test.main();
sidebar_icon_test.main();
}
// database
// don't remove this test, it can prevent the test from failing.
{
preset_af_cloud_env_test.main();
database_test_runner.main();
}
// document
// don't remove this test, it can prevent the test from failing.
{
preset_af_cloud_env_test.main();
document_test_runner.main();
}
}
@@ -1,33 +1,10 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'dart:ui';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:intl/intl.dart';
import 'package:path/path.dart' as p;
import '../../../shared/dir.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../board/board_hide_groups_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -38,7 +15,6 @@ void main() {
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapContinousAnotherWay();
await tester.tapAnonymousSignInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
@@ -54,12 +30,6 @@ void main() {
await tester.enterUserName('local_user');
// Scroll to sign-in
await tester.scrollUntilVisible(
find.byType(AccountSignInOutButton),
100,
scrollable: find.findSettingsScrollable(),
);
await tester.tapButton(find.byType(AccountSignInOutButton));
// sign up with Google
@@ -1,5 +1,3 @@
import 'anon_user_data_migration_test.dart' as anon_user_test;
void main() async {
anon_user_test.main();
// anon_user_test.main();
}
@@ -0,0 +1,80 @@
import 'dart:io';
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart'
hide UploadImageMenu, ResizableImage;
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../../../shared/constants.dart';
import '../../../shared/database_test_op.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// copy link to block
group('database image:', () {
testWidgets('insert image', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// open the first row detail page and upload an image
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Grid,
pageName: 'database image',
);
await tester.openFirstRowDetailPage();
// insert an image block
{
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_image.tr(),
);
}
// upload an image
{
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final tempDirectory = await getTemporaryDirectory();
final imagePath = p.join(tempDirectory.path, 'sample.jpeg');
final file = File(imagePath)
..writeAsBytesSync(image.buffer.asUint8List());
mockPickFilePaths(
paths: [imagePath],
);
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
await tester.tapButtonWithName(
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
);
await tester.pumpAndSettle();
expect(find.byType(ResizableImage), findsOneWidget);
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
// remove the temp file
file.deleteSync();
}
});
});
}
@@ -0,0 +1,9 @@
import 'package:integration_test/integration_test.dart';
import 'database_image_test.dart' as database_image_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
database_image_test.main();
}
@@ -0,0 +1,234 @@
import 'package:appflowy/ai/service/ai_entities.dart';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/ai_test_op.dart';
import '../../../shared/constants.dart';
import '../../../shared/mock/mock_ai.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('AI Writer:', () {
testWidgets('the ai writer transaction should only apply in memory',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
aiRepositoryBuilder: () => MockAIRepository(),
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_aiWriter.tr(),
);
expect(find.byType(AiWriterBlockComponent), findsOneWidget);
// switch to another page
await tester.openPage(Constants.gettingStartedPageName);
// switch back to the page
await tester.openPage(pageName);
// expect the ai writer block is not in the document
expect(find.byType(AiWriterBlockComponent), findsNothing);
});
testWidgets('Improve writing', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
await tester.editor.tapLineOfEditorAt(0);
// insert a paragraph
final text = 'I have an apple';
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText(text);
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: text.length),
),
);
await tester.pumpAndSettle();
await tester.tapButton(find.byType(ImproveWritingButton));
final editorState = tester.editor.getCurrentEditorState();
final document = editorState.document;
expect(document.root.children.length, 3);
expect(document.root.children[1].type, ParagraphBlockKeys.type);
expect(
document.root.children[1].delta!.toPlainText(),
'I have an apple and a banana',
);
});
testWidgets('fix grammar', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
await tester.editor.tapLineOfEditorAt(0);
// insert a paragraph
final text = 'We didn’t had enough money';
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText(text);
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: text.length),
),
);
await tester.pumpAndSettle();
await tester.selectAIWriter(AiWriterCommand.fixSpellingAndGrammar);
final editorState = tester.editor.getCurrentEditorState();
final document = editorState.document;
expect(document.root.children.length, 3);
expect(document.root.children[1].type, ParagraphBlockKeys.type);
expect(
document.root.children[1].delta!.toPlainText(),
'We didn’t have enough money',
);
});
testWidgets('ask ai', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudDevelop,
aiRepositoryBuilder: () => MockAIRepository(
validator: _CompletionHistoryValidator(),
),
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
await tester.editor.tapLineOfEditorAt(0);
// insert a paragraph
final text = 'What is TPU?';
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText(text);
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: text.length),
),
);
await tester.pumpAndSettle();
await tester.selectAIWriter(AiWriterCommand.userQuestion);
await tester.enterTextInPromptTextField("Explain the concept of TPU");
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
// await tester.selectModel("GPT-4o-mini");
await tester.enterTextInPromptTextField("How about GPU?");
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
});
});
}
class _CompletionHistoryValidator extends StreamCompletionValidator {
static const _expectedResponses = {
1: "What is TPU?",
3: [
"What is TPU?",
"Explain the concept of TPU",
"TPU is a tensor processing unit that is designed to accelerate machine",
],
5: [
"What is TPU?",
"Explain the concept of TPU",
"TPU is a tensor processing unit that is designed to accelerate machine",
"How about GPU?",
"GPU is a graphics processing unit that is designed to accelerate machine learning tasks.",
],
};
@override
bool validate(
String text,
String? objectId,
CompletionTypePB completionType,
PredefinedFormat? format,
List<String> sourceIds,
List<AiWriterRecord> history,
) {
assert(completionType == CompletionTypePB.UserQuestion);
if (history.isEmpty) return false;
final expectedMessages = _expectedResponses[history.length];
if (expectedMessages == null) return false;
if (expectedMessages is String) {
_assertMessage(history[0].content, expectedMessages);
return true;
} else if (expectedMessages is List<String>) {
for (var i = 0; i < expectedMessages.length; i++) {
_assertMessage(history[i].content, expectedMessages[i]);
}
return true;
}
return false;
}
void _assertMessage(String actual, String expected) {
assert(
actual.trim() == expected,
"expected '$expected', but got '${actual.trim()}'",
);
}
}
@@ -0,0 +1,275 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/constants.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// copy link to block
group('copy link to block:', () {
testWidgets('copy link to check if the clipboard has the correct content',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// open getting started page
await tester.openPage(Constants.gettingStartedPageName);
await tester.editor.copyLinkToBlock([0]);
await tester.pumpAndSettle(Durations.short1);
// check the clipboard
final content = await Clipboard.getData(Clipboard.kTextPlain);
expect(
content?.text,
matches(appflowySharePageLinkPattern),
);
});
testWidgets('copy link to block(another page) and paste it in doc',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// open getting started page
await tester.openPage(Constants.gettingStartedPageName);
await tester.editor.copyLinkToBlock([0]);
// create a new page and paste it
const pageName = 'copy link to block';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// paste the link to the new page
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.paste();
await tester.pumpAndSettle();
// check the content of the block
final node = tester.editor.getNodeAtPath([0]);
final delta = node.delta!;
final insert = (delta.first as TextInsert).text;
final attributes = delta.first.attributes;
expect(insert, MentionBlockKeys.mentionChar);
final mention =
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
expect(mention[MentionBlockKeys.type], MentionType.page.name);
expect(mention[MentionBlockKeys.blockId], isNotNull);
expect(mention[MentionBlockKeys.pageId], isNotNull);
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.textContaining(
Constants.gettingStartedPageName,
findRichText: true,
),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.textContaining(
// the pasted block content is 'Welcome to AppFlowy'
'Welcome to AppFlowy',
findRichText: true,
),
),
findsOneWidget,
);
// tap the mention block to jump to the page
await tester.tapButton(find.byType(MentionPageBlock));
await tester.pumpAndSettle();
// expect to go to the getting started page
final documentPage = find.byType(DocumentPage);
expect(documentPage, findsOneWidget);
expect(
tester.widget<DocumentPage>(documentPage).view.name,
Constants.gettingStartedPageName,
);
// and the block is selected
expect(
tester.widget<DocumentPage>(documentPage).initialBlockId,
mention[MentionBlockKeys.blockId],
);
expect(
tester.editor.getCurrentEditorState().selection,
Selection.collapsed(
Position(
path: [0],
),
),
);
});
testWidgets('copy link to block(same page) and paste it in doc',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// create a new page and paste it
const pageName = 'copy link to block';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// copy the link to block from the first line
const inputText = 'Hello World';
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText(inputText);
await tester.ime.insertCharacter('\n');
await tester.pumpAndSettle();
await tester.editor.copyLinkToBlock([0]);
// paste the link to the second line
await tester.editor.tapLineOfEditorAt(1);
await tester.editor.paste();
await tester.pumpAndSettle();
// check the content of the block
final node = tester.editor.getNodeAtPath([1]);
final delta = node.delta!;
final insert = (delta.first as TextInsert).text;
final attributes = delta.first.attributes;
expect(insert, MentionBlockKeys.mentionChar);
final mention =
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
expect(mention[MentionBlockKeys.type], MentionType.page.name);
expect(mention[MentionBlockKeys.blockId], isNotNull);
expect(mention[MentionBlockKeys.pageId], isNotNull);
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.textContaining(
inputText,
findRichText: true,
),
),
findsNWidgets(2),
);
// edit the pasted block
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('!');
await tester.pumpAndSettle();
// check the content of the block
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.textContaining(
'$inputText!',
findRichText: true,
),
),
findsNWidgets(2),
);
// tap the mention block
await tester.tapButton(find.byType(MentionPageBlock));
expect(
tester.editor.getCurrentEditorState().selection,
Selection.collapsed(
Position(
path: [0],
),
),
);
});
testWidgets('''1. copy link to block from another page
2. paste the link to the new page
3. delete the original page
4. check the content of the block, it should be no access to the page
''', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// open getting started page
await tester.openPage(Constants.gettingStartedPageName);
await tester.editor.copyLinkToBlock([0]);
// create a new page and paste it
const pageName = 'copy link to block';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// paste the link to the new page
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.paste();
await tester.pumpAndSettle();
// tap the mention block to jump to the page
await tester.tapButton(find.byType(MentionPageBlock));
await tester.pumpAndSettle();
// expect to go to the getting started page
final documentPage = find.byType(DocumentPage);
expect(documentPage, findsOneWidget);
expect(
tester.widget<DocumentPage>(documentPage).view.name,
Constants.gettingStartedPageName,
);
// delete the getting started page
await tester.hoverOnPageName(
Constants.gettingStartedPageName,
onHover: () async => tester.tapDeletePageButton(),
);
tester.expectToSeeDocumentBanner();
tester.expectNotToSeePageName(gettingStarted);
// delete the page permanently
await tester.tapDeletePermanentlyButton();
// go back the page
await tester.openPage(pageName);
await tester.pumpAndSettle();
// check the content of the block
// it should be no access to the page
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.findTextInFlowyText(
LocaleKeys.document_mention_noAccess.tr(),
),
),
findsOneWidget,
);
});
});
}
@@ -57,7 +57,7 @@ void main() {
// move the checkbox to the child of the block at path [9]
await tester.editor.dragBlock(
[10],
const Offset(80, -30),
const Offset(120, -20),
);
// wait for the move animation to complete
@@ -68,38 +68,6 @@ void main() {
expect(afterMoveBlock.delta, beforeMoveBlock.delta);
});
// Copy link to block feature is disable temporarily, enable this test when the feature is ready.
// testWidgets('copy block link', (tester) async {
// await tester.initializeAppFlowy(
// cloudType: AuthenticatorType.appflowyCloudSelfHost,
// );
// await tester.tapGoogleLoginInButton();
// await tester.expectToSeeHomePageWithGetStartedPage();
// // open getting started page
// await tester.openPage(Constants.gettingStartedPageName);
// // hover and click on the option menu button beside the block component.
// await tester.editor.hoverAndClickOptionMenuButton([0]);
// // click the copy link to block option
// await tester.tap(
// find.findTextInFlowyText(
// LocaleKeys.document_plugins_optionAction_copyLinkToBlock.tr(),
// ),
// );
// await tester.pumpAndSettle(Durations.short1);
// // check the clipboard
// final content = await Clipboard.getData(Clipboard.kTextPlain);
// expect(
// content?.text,
// matches(
// r'^https:\/\/appflowy\.com\/app\/[a-f0-9-]{36}\/[a-f0-9-]{36}\?blockId=[A-Za-z0-9_-]+$',
// ),
// );
// });
testWidgets('hover on the block and delete it', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
@@ -0,0 +1,220 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/shared/share/publish_tab.dart';
import 'package:appflowy/plugins/shared/share/share_menu.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/constants.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Publish:', () {
testWidgets('publish document', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// open the publish menu
await tester.openPublishMenu();
// publish the document
final publishButton = find.byType(PublishButton);
final unpublishButton = find.byType(UnPublishButton);
await tester.tapButton(publishButton);
// expect to see unpublish, visit site and manage all sites button
expect(unpublishButton, findsOneWidget);
expect(find.text(LocaleKeys.shareAction_visitSite.tr()), findsOneWidget);
// unpublish the document
await tester.tapButton(unpublishButton);
// expect to see publish button
expect(publishButton, findsOneWidget);
});
testWidgets('rename path name', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// open the publish menu
await tester.openPublishMenu();
// publish the document
final publishButton = find.byType(PublishButton);
await tester.tapButton(publishButton);
// rename the path name
final inputField = find.descendant(
of: find.byType(ShareMenu),
matching: find.byType(TextField),
);
// rename with invalid name
await tester.tap(inputField);
await tester.enterText(inputField, '&&&&????');
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
await tester.pumpAndSettle();
// expect to see the toast with error message
final errorToast1 = find.text(
LocaleKeys.settings_sites_error_publishNameContainsInvalidCharacters
.tr(),
);
await tester.pumpUntilFound(errorToast1);
await tester.pumpUntilNotFound(errorToast1);
// rename with long name
await tester.tap(inputField);
await tester.enterText(inputField, 'long-path-name' * 200);
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
await tester.pumpAndSettle();
// expect to see the toast with error message
final errorToast2 = find.text(
LocaleKeys.settings_sites_error_publishNameTooLong.tr(),
);
await tester.pumpUntilFound(errorToast2);
await tester.pumpUntilNotFound(errorToast2);
// rename with empty name
await tester.tap(inputField);
await tester.enterText(inputField, '');
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
await tester.pumpAndSettle();
// expect to see the toast with error message
final errorToast3 = find.text(
LocaleKeys.settings_sites_error_publishNameCannotBeEmpty.tr(),
);
await tester.pumpUntilFound(errorToast3);
await tester.pumpUntilNotFound(errorToast3);
// input the new path name
await tester.tap(inputField);
await tester.enterText(inputField, 'new-path-name');
// click save button
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
await tester.pumpAndSettle();
// expect to see the toast with success message
final successToast = find.text(
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
);
await tester.pumpUntilFound(successToast);
await tester.pumpUntilNotFound(successToast);
// click the copy link button
await tester.tapButton(
find.byWidgetPredicate(
(widget) =>
widget is FlowySvg &&
widget.svg.path == FlowySvgs.m_toolbar_link_m.path,
),
);
await tester.pumpAndSettle();
// check the clipboard has the link
final content = await Clipboard.getData(Clipboard.kTextPlain);
expect(
content?.text?.contains('new-path-name'),
isTrue,
);
});
testWidgets('re-publish the document', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// open the publish menu
await tester.openPublishMenu();
// publish the document
final publishButton = find.byType(PublishButton);
await tester.tapButton(publishButton);
// rename the path name
final inputField = find.descendant(
of: find.byType(ShareMenu),
matching: find.byType(TextField),
);
// input the new path name
const newName = 'new-path-name';
await tester.enterText(inputField, newName);
// click save button
await tester.tapButton(find.text(LocaleKeys.button_save.tr()));
await tester.pumpAndSettle();
// expect to see the toast with success message
final successToast = find.text(
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
);
await tester.pumpUntilNotFound(successToast);
// unpublish the document
final unpublishButton = find.byType(UnPublishButton);
await tester.tapButton(unpublishButton);
final unpublishSuccessToast = find.text(
LocaleKeys.publish_unpublishSuccessfully.tr(),
);
await tester.pumpUntilNotFound(unpublishSuccessToast);
// re-publish the document
await tester.tapButton(publishButton);
// expect to see the toast with success message
final rePublishSuccessToast = find.text(
LocaleKeys.publish_publishSuccessfully.tr(),
);
await tester.pumpUntilNotFound(rePublishSuccessToast);
// check the clipboard has the link
final content = await Clipboard.getData(Clipboard.kTextPlain);
expect(
content?.text?.contains(newName),
isTrue,
);
});
});
}
@@ -1,9 +1,16 @@
import 'package:integration_test/integration_test.dart';
import 'document_ai_writer_test.dart' as document_ai_writer_test;
import 'document_copy_link_to_block_test.dart'
as document_copy_link_to_block_test;
import 'document_option_actions_test.dart' as document_option_actions_test;
import 'document_publish_test.dart' as document_publish_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
document_option_actions_test.main();
document_copy_link_to_block_test.main();
document_publish_test.main();
document_ai_writer_test.main();
}
@@ -8,11 +8,13 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Empty', () {
testWidgets('set appflowy cloud', (tester) async {
group('Preset cloud env', () {
testWidgets('use self-hosted cloud', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.pumpAndSettle();
});
});
}
@@ -0,0 +1,62 @@
import 'dart:convert';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart';
import 'package:flowy_svg/flowy_svg.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/emoji.dart';
import '../../../shared/util.dart';
void main() {
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
testWidgets('Change slide bar space icon', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
final emojiIconData = await tester.loadIcon();
final firstIcon = IconsData.fromJson(jsonDecode(emojiIconData.emoji));
await tester.hoverOnWidget(
find.byType(SidebarSpaceHeader),
onHover: () async {
final moreOption = find.byType(SpaceMorePopup);
await tester.tapButton(moreOption);
expect(find.byType(FlowyIconEmojiPicker), findsNothing);
await tester.tapSvgButton(SpaceMoreActionType.changeIcon.leftIconSvg);
expect(find.byType(FlowyIconEmojiPicker), findsOneWidget);
},
);
final icons = find.byWidgetPredicate(
(w) => w is FlowySvg && w.svgString == firstIcon.svgString,
);
expect(icons, findsOneWidget);
await tester.tapIcon(EmojiIconData.icon(firstIcon));
final spaceHeader = find.byType(SidebarSpaceHeader);
final spaceIcon = find.descendant(
of: spaceHeader,
matching: find.byWidgetPredicate(
(w) => w is FlowySvg && w.svgString == firstIcon.svgString,
),
);
expect(spaceIcon, findsOneWidget);
});
}
@@ -1,34 +1,13 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:universal_platform/universal_platform.dart';
import '../../../shared/constants.dart';
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
@@ -92,6 +71,14 @@ void main() {
await tester.pumpAndSettle();
}
// Attempt right-click on the page name and expect not to see
await tester.tap(gettingStarted.last, buttons: kSecondaryButton);
await tester.pumpAndSettle();
expect(
find.text(LocaleKeys.disclosureAction_moveTo.tr()),
findsOneWidget,
);
// move the current page to Getting started
await tester.tapButton(
gettingStarted.last,
@@ -0,0 +1,55 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text_input.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/constants.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Rename empty name view (untitled)', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
);
// click the ... button and open rename dialog
await tester.hoverOnPageName(
ViewLayoutPB.Document.defaultName,
onHover: () async {
await tester.tapPageOptionButton();
await tester.tapButtonWithName(
LocaleKeys.disclosureAction_rename.tr(),
);
},
);
await tester.pumpAndSettle();
expect(find.byType(NavigatorTextFieldDialog), findsOneWidget);
final textField = tester.widget<FlowyFormTextInput>(
find.descendant(
of: find.byType(NavigatorTextFieldDialog),
matching: find.byType(FlowyFormTextInput),
),
);
expect(
textField.controller!.text,
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
);
});
}
@@ -1,21 +1,12 @@
// ignore_for_file: unused_import
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
@@ -48,8 +39,8 @@ void main() {
);
await tester.tapButton(find.byType(AccountSignInOutButton));
tester.expectToSeeText(LocaleKeys.button_ok.tr());
await tester.tapButtonWithName(LocaleKeys.button_ok.tr());
tester.expectToSeeText(LocaleKeys.button_yes.tr());
await tester.tapButtonWithName(LocaleKeys.button_yes.tr());
// Go to the sign in page again
await tester.pumpAndSettle(const Duration(seconds: 5));
@@ -66,12 +57,6 @@ void main() {
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.account);
// Scroll to sign-in
await tester.scrollUntilVisible(
find.byType(AccountSignInOutButton),
100,
scrollable: find.findSettingsScrollable(),
);
await tester.tapButton(find.byType(AccountSignInOutButton));
tester.expectToSeeGoogleLoginButton();
@@ -1,24 +1,9 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/dir.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
@@ -1,33 +1,11 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/pages/account/account_user_profile.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../board/board_hide_groups_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -1,21 +1,12 @@
// ignore_for_file: unused_import
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../../shared/workspace.dart';
@@ -49,6 +40,10 @@ void main() {
await tester.changeWorkspaceIcon(icon);
await tester.changeWorkspaceName(name);
await tester.pumpUntilNotFound(
find.text(LocaleKeys.workspace_renameSuccess.tr()),
);
workspaceIcon = tester.widget<WorkspaceIcon>(
find.byType(WorkspaceIcon),
);
@@ -1,38 +1,20 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/shared/loading.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('collaborative workspace: ', () {
group('collaborative workspace:', () {
// combine the create and delete workspace test to reduce the time
testWidgets('create a new workspace, open it and then delete it',
(tester) async {
@@ -93,5 +75,138 @@ void main() {
},
);
});
testWidgets('check the member count immediately after creating a workspace',
(tester) async {
// only run the test when the feature flag is on
if (!FeatureFlag.collaborativeWorkspace.isOn) {
return;
}
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const name = 'AppFlowy.IO';
// the workspace will be opened after created
await tester.createCollaborativeWorkspace(name);
final loading = find.byType(Loading);
await tester.pumpUntilNotFound(loading);
await tester.openCollaborativeWorkspaceMenu();
// expect to see the member count
final memberCount = find.text('1 member');
expect(memberCount, findsNWidgets(2));
});
testWidgets('workspace menu popover behavior test', (tester) async {
// only run the test when the feature flag is on
if (!FeatureFlag.collaborativeWorkspace.isOn) {
return;
}
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const name = 'AppFlowy.IO';
// the workspace will be opened after created
await tester.createCollaborativeWorkspace(name);
final loading = find.byType(Loading);
await tester.pumpUntilNotFound(loading);
await tester.openCollaborativeWorkspaceMenu();
// hover on the workspace and click the more button
final workspaceItem = find.byWidgetPredicate(
(w) => w is WorkspaceMenuItem && w.workspace.name == name,
);
// the workspace menu shouldn't conflict with logout
await tester.hoverOnWidget(
workspaceItem,
onHover: () async {
final moreButton = find.byWidgetPredicate(
(w) => w is WorkspaceMoreActionList && w.workspace.name == name,
);
expect(moreButton, findsOneWidget);
await tester.tapButton(moreButton);
expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget);
final logoutButton = find.byType(WorkspaceMoreButton);
await tester.tapButton(logoutButton);
expect(find.text(LocaleKeys.button_logout.tr()), findsOneWidget);
expect(moreButton, findsNothing);
await tester.tapButton(moreButton);
expect(find.text(LocaleKeys.button_logout.tr()), findsNothing);
expect(moreButton, findsOneWidget);
},
);
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// clicking on the more action button for the same workspace shouldn't do
// anything
await tester.openCollaborativeWorkspaceMenu();
await tester.hoverOnWidget(
workspaceItem,
onHover: () async {
final moreButton = find.byWidgetPredicate(
(w) => w is WorkspaceMoreActionList && w.workspace.name == name,
);
expect(moreButton, findsOneWidget);
await tester.tapButton(moreButton);
expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget);
// click it again
await tester.tapButton(moreButton);
// nothing should happen
expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget);
},
);
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// clicking on the more button of another workspace should close the menu
// for this one
await tester.openCollaborativeWorkspaceMenu();
final moreButton = find.byWidgetPredicate(
(w) => w is WorkspaceMoreActionList && w.workspace.name == name,
);
await tester.hoverOnWidget(
workspaceItem,
onHover: () async {
expect(moreButton, findsOneWidget);
await tester.tapButton(moreButton);
expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget);
},
);
final otherWorspaceItem = find.byWidgetPredicate(
(w) => w is WorkspaceMenuItem && w.workspace.name != name,
);
final otherMoreButton = find.byWidgetPredicate(
(w) => w is WorkspaceMoreActionList && w.workspace.name != name,
);
await tester.hoverOnWidget(
otherWorspaceItem,
onHover: () async {
expect(otherMoreButton, findsOneWidget);
await tester.tapButton(otherMoreButton);
expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget);
expect(moreButton, findsNothing);
},
);
});
});
}
@@ -1,28 +1,17 @@
// ignore_for_file: unused_import
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/shared/share/constants.dart';
import 'package:appflowy/plugins/shared/share/share_menu.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/shared/patterns/common_patterns.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/constants.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../../shared/workspace.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -61,11 +50,11 @@ void main() {
final plainText = clipboardContent.plainText;
expect(
plainText,
startsWith(ShareConstants.shareBaseUrl),
matches(appflowySharePageLinkPattern),
);
final shareValues = plainText!
.replaceAll('${ShareConstants.shareBaseUrl}/', '')
.replaceAll('${ShareConstants.defaultBaseWebDomain}/app/', '')
.split('/');
final workspaceId = shareValues[0];
expect(workspaceId, isNotEmpty);
@@ -0,0 +1,87 @@
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/shared/loading.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/constants.dart';
import '../../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Tabs', () {
testWidgets('close other tabs before opening a new workspace',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const name = 'AppFlowy.IO';
// the workspace will be opened after created
await tester.createCollaborativeWorkspace(name);
final loading = find.byType(Loading);
await tester.pumpUntilNotFound(loading);
// create new tabs in the workspace
expect(find.byType(FlowyTab), findsNothing);
const documentOneName = 'document one';
const documentTwoName = 'document two';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: documentOneName,
);
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: documentTwoName,
);
/// Open second menu item in a new tab
await tester.openAppInNewTab(documentOneName, ViewLayoutPB.Document);
/// Open third menu item in a new tab
await tester.openAppInNewTab(documentTwoName, ViewLayoutPB.Document);
expect(
find.descendant(
of: find.byType(TabsManager),
matching: find.byType(FlowyTab),
),
findsNWidgets(2),
);
// switch to the another workspace
final Finder items = find.byType(WorkspaceMenuItem);
await tester.openCollaborativeWorkspaceMenu();
await tester.pumpUntilFound(items);
expect(items, findsNWidgets(2));
// open the first workspace
await tester.tap(items.first);
await tester.pumpUntilNotFound(loading);
expect(find.byType(FlowyTab), findsNothing);
});
testWidgets('the space view should not be opened', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
expect(find.byType(AppFlowyEditorPage), findsOneWidget);
expect(find.text('Blank page'), findsNothing);
});
});
}
@@ -1,35 +1,11 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:universal_platform/universal_platform.dart';
import '../../../shared/constants.dart';
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/util.dart';
import '../../../shared/workspace.dart';
@@ -1,36 +1,23 @@
// ignore_for_file: unused_import
import 'dart:io';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/plugins/shared/share/publish_tab.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/sites/domain/domain_more_action.dart';
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_item.dart';
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_more_action.dart';
import 'package:appflowy/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import '../../../shared/database_test_op.dart';
import '../../../shared/dir.dart';
import '../../../shared/emoji.dart';
import '../../../shared/mock/mock_file_picker.dart';
import '../../../shared/constants.dart';
import '../../../shared/util.dart';
void main() {
@@ -93,4 +80,274 @@ void main() {
},
);
});
group('sites settings:', () {
testWidgets(
'manage published page, set it as homepage, remove the homepage',
(tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// open the publish menu
await tester.openPublishMenu();
// publish the document
await tester.tapButton(find.byType(PublishButton));
// click empty area to close the publish menu
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
// check if the page is published in sites page
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.sites);
// wait the backend return the sites data
await tester.wait(1000);
// check if the page is published in sites page
final pageItem = find.byWidgetPredicate(
(widget) =>
widget is PublishedViewItem &&
widget.publishInfoView.view.name == pageName,
);
if (pageItem.evaluate().isEmpty) {
return;
}
expect(pageItem, findsOneWidget);
// comment it out because it's not allowed to update the namespace in free plan
// // set it to homepage
// await tester.tapButton(
// find.textContaining(
// LocaleKeys.settings_sites_selectHomePage.tr(),
// ),
// );
// await tester.tapButton(
// find.descendant(
// of: find.byType(SelectHomePageMenu),
// matching: find.text(pageName),
// ),
// );
// await tester.pumpAndSettle();
// // check if the page is set to homepage
// final homePageItem = find.descendant(
// of: find.byType(DomainItem),
// matching: find.text(pageName),
// );
// expect(homePageItem, findsOneWidget);
// // remove the homepage
// await tester.tapButton(find.byType(DomainMoreAction));
// await tester.tapButton(
// find.text(LocaleKeys.settings_sites_removeHomepage.tr()),
// );
// await tester.pumpAndSettle();
// // check if the page is removed from homepage
// expect(homePageItem, findsNothing);
});
testWidgets('update namespace', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
// check if the page is published in sites page
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.sites);
// wait the backend return the sites data
await tester.wait(1000);
// update the domain
final domainMoreAction = find.byType(DomainMoreAction);
await tester.tapButton(domainMoreAction);
final updateNamespaceButton = find.text(
LocaleKeys.settings_sites_updateNamespace.tr(),
);
await tester.pumpUntilFound(updateNamespaceButton);
// click the update namespace button
await tester.tapButton(updateNamespaceButton);
// comment it out because it's not allowed to update the namespace in free plan
// expect to see the dialog
// await tester.updateNamespace('&&&???');
// // need to upgrade to pro plan to update the namespace
// final errorToast = find.text(
// LocaleKeys.settings_sites_error_proPlanLimitation.tr(),
// );
// await tester.pumpUntilFound(errorToast);
// expect(errorToast, findsOneWidget);
// await tester.pumpUntilNotFound(errorToast);
// comment it out because it's not allowed to update the namespace in free plan
// // short namespace
// await tester.updateNamespace('a');
// // expect to see the toast with error message
// final errorToast2 = find.text(
// LocaleKeys.settings_sites_error_namespaceTooShort.tr(),
// );
// await tester.pumpUntilFound(errorToast2);
// expect(errorToast2, findsOneWidget);
// await tester.pumpUntilNotFound(errorToast2);
// // valid namespace
// await tester.updateNamespace('AppFlowy');
// // expect to see the toast with success message
// final successToast = find.text(
// LocaleKeys.settings_sites_success_namespaceUpdated.tr(),
// );
// await tester.pumpUntilFound(successToast);
// expect(successToast, findsOneWidget);
});
testWidgets('''
More actions for published page:
1. visit site
2. copy link
3. settings
4. unpublish
5. custom url
''', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
const pageName = 'Document';
await tester.createNewPageInSpace(
spaceName: Constants.generalSpaceName,
layout: ViewLayoutPB.Document,
pageName: pageName,
);
// open the publish menu
await tester.openPublishMenu();
// publish the document
await tester.tapButton(find.byType(PublishButton));
// click empty area to close the publish menu
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
// check if the page is published in sites page
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.sites);
// wait the backend return the sites data
await tester.wait(2000);
// check if the page is published in sites page
final pageItem = find.byWidgetPredicate(
(widget) =>
widget is PublishedViewItem &&
widget.publishInfoView.view.name == pageName,
);
if (pageItem.evaluate().isEmpty) {
return;
}
expect(pageItem, findsOneWidget);
final copyLinkItem = find.text(LocaleKeys.shareAction_copyLink.tr());
final customUrlItem = find.text(LocaleKeys.settings_sites_customUrl.tr());
final unpublishItem = find.text(LocaleKeys.shareAction_unPublish.tr());
// custom url
final publishMoreAction = find.byType(PublishedViewMoreAction);
// click the copy link button
{
await tester.tapButton(publishMoreAction);
await tester.pumpAndSettle();
await tester.pumpUntilFound(copyLinkItem);
await tester.tapButton(copyLinkItem);
await tester.pumpAndSettle();
await tester.pumpUntilNotFound(copyLinkItem);
final clipboardContent = await getIt<ClipboardService>().getData();
final plainText = clipboardContent.plainText;
expect(
plainText,
contains(pageName),
);
}
// custom url
{
await tester.tapButton(publishMoreAction);
await tester.pumpAndSettle();
await tester.pumpUntilFound(customUrlItem);
await tester.tapButton(customUrlItem);
await tester.pumpAndSettle();
await tester.pumpUntilNotFound(customUrlItem);
// see the custom url dialog
final customUrlDialog = find.byType(PublishedViewSettingsDialog);
expect(customUrlDialog, findsOneWidget);
// rename the custom url
final textField = find.descendant(
of: customUrlDialog,
matching: find.byType(TextField),
);
await tester.enterText(textField, 'hello-world');
await tester.pumpAndSettle();
// click the save button
final saveButton = find.descendant(
of: customUrlDialog,
matching: find.text(LocaleKeys.button_save.tr()),
);
await tester.tapButton(saveButton);
await tester.pumpAndSettle();
// expect to see the toast with success message
final successToast = find.text(
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
);
await tester.pumpUntilFound(successToast);
expect(successToast, findsOneWidget);
}
// unpublish
{
await tester.tapButton(publishMoreAction);
await tester.pumpAndSettle();
await tester.pumpUntilFound(unpublishItem);
await tester.tapButton(unpublishItem);
await tester.pumpAndSettle();
await tester.pumpUntilNotFound(unpublishItem);
// expect to see the toast with success message
final successToast = find.text(
LocaleKeys.publish_unpublishSuccessfully.tr(),
);
await tester.pumpUntilFound(successToast);
expect(successToast, findsOneWidget);
await tester.pumpUntilNotFound(successToast);
// check if the page is unpublished in sites page
expect(pageItem, findsNothing);
}
});
});
}
@@ -3,6 +3,7 @@ import 'package:integration_test/integration_test.dart';
import 'change_name_and_icon_test.dart' as change_name_and_icon_test;
import 'collaborative_workspace_test.dart' as collaborative_workspace_test;
import 'share_menu_test.dart' as share_menu_test;
import 'tabs_test.dart' as tabs_test;
import 'workspace_icon_test.dart' as workspace_icon_test;
import 'workspace_settings_test.dart' as workspace_settings_test;
@@ -14,4 +15,5 @@ void main() {
collaborative_workspace_test.main();
change_name_and_icon_test.main();
workspace_icon_test.main();
tabs_test.main();
}
@@ -1,6 +1,15 @@
import 'dart:convert';
import 'dart:math';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_cell.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -8,7 +17,9 @@ import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
});
group('Folder Search', () {
testWidgets('Search for views', (tester) async {
@@ -33,21 +44,106 @@ void main() {
await tester.pumpAndSettle(const Duration(milliseconds: 200));
// Expect two search results "ViewOna" and "ViewOne" (Distance 1 to ViewOna)
expect(find.byType(SearchResultTile), findsNWidgets(2));
expect(find.byType(SearchResultCell), findsNWidgets(2));
// The score should be higher for "ViewOna" thus it should be shown first
final secondDocumentWidget = tester
.widget(find.byType(SearchResultTile).first) as SearchResultTile;
expect(secondDocumentWidget.result.data, secondDocument);
.widget(find.byType(SearchResultCell).first) as SearchResultCell;
expect(secondDocumentWidget.item.displayName, secondDocument);
// Change search to "ViewOne"
await tester.enterText(searchFieldFinder, firstDocument);
await tester.pumpAndSettle(const Duration(seconds: 1));
// The score should be higher for "ViewOne" thus it should be shown first
final firstDocumentWidget = tester
.widget(find.byType(SearchResultTile).first) as SearchResultTile;
expect(firstDocumentWidget.result.data, firstDocument);
final firstDocumentWidget = tester.widget(
find.byType(SearchResultCell).first,
) as SearchResultCell;
expect(firstDocumentWidget.item.displayName, firstDocument);
});
testWidgets('Displaying icons in search results', (tester) async {
final randomValue = Random().nextInt(10000) + 10000;
final pageNames = ['First Page-$randomValue', 'Second Page-$randomValue'];
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final emojiIconData = await tester.loadIcon();
/// create two pages
for (final pageName in pageNames) {
await tester.createNewPageWithNameUnderParent(name: pageName);
await tester.updatePageIconInTitleBarByName(
name: pageName,
layout: ViewLayoutPB.Document,
icon: emojiIconData,
);
}
await tester.toggleCommandPalette();
/// search for `Page`
final searchFieldFinder = find.descendant(
of: find.byType(SearchField),
matching: find.byType(FlowyTextField),
);
await tester.enterText(searchFieldFinder, 'Page-$randomValue');
await tester.pumpAndSettle(const Duration(milliseconds: 200));
expect(find.byType(SearchResultCell), findsNWidgets(2));
/// check results
final svgs = find.descendant(
of: find.byType(SearchResultCell),
matching: find.byType(FlowySvg),
);
expect(svgs, findsNWidgets(2));
final firstSvg = svgs.first.evaluate().first.widget as FlowySvg,
lastSvg = svgs.last.evaluate().first.widget as FlowySvg;
final iconData = IconsData.fromJson(jsonDecode(emojiIconData.emoji));
/// icon displayed correctly
expect(firstSvg.svgString, iconData.svgString);
expect(lastSvg.svgString, iconData.svgString);
testWidgets('select the content in document and search', (tester) async {
const firstDocument = ''; // empty document
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: firstDocument);
await tester.editor.updateSelection(
Selection(
start: Position(
path: [0],
),
end: Position(
path: [0],
offset: 10,
),
),
);
await tester.pumpAndSettle();
expect(
find.byType(FloatingToolbar),
findsOneWidget,
);
await tester.toggleCommandPalette();
expect(find.byType(CommandPaletteModal), findsOneWidget);
expect(
find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()),
findsOneWidget,
);
expect(
find.text(firstDocument),
findsOneWidget,
);
});
});
});
}
@@ -1,5 +1,5 @@
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_views_list.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -27,11 +27,12 @@ void main() {
expect(find.byType(RecentViewsList), findsOneWidget);
// Expect three recent history items
expect(find.byType(RecentViewTile), findsNWidgets(3));
expect(find.byType(SearchRecentViewCell), findsNWidgets(3));
// Expect the first item to be the last viewed document
final firstDocumentWidget =
tester.widget(find.byType(RecentViewTile).first) as RecentViewTile;
tester.widget(find.byType(SearchRecentViewCell).first)
as SearchRecentViewCell;
expect(firstDocumentWidget.view.name, secondDocument);
});
});
@@ -1,5 +1,6 @@
import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -9,7 +10,14 @@ import '../../shared/database_test_op.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('calendar', () {
testWidgets('update calendar layout', (tester) async {
@@ -277,5 +285,74 @@ void main() {
tester.assertRowDetailPageOpened();
});
testWidgets('filter calendar events', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// Create the calendar view
await tester.createNewPageWithNameUnderParent(
layout: ViewLayoutPB.Calendar,
);
// Create a new event on the first of this month
final today = DateTime.now();
final firstOfThisMonth = DateTime(today.year, today.month);
await tester.doubleClickCalendarCell(firstOfThisMonth);
await tester.dismissEventEditor();
tester.assertNumberOfEventsInCalendar(1);
await tester.openCalendarEvent(index: 0, date: firstOfThisMonth);
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
await tester.createOption(name: "asdf");
await tester.createOption(name: "qwer");
await tester.selectOption(name: "asdf");
await tester.dismissCellEditor();
await tester.dismissCellEditor();
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(FieldType.MultiSelect, "Tags");
await tester.tapFilterButtonInGrid('Tags');
await tester.tapOptionFilterWithName('asdf');
await tester.dismissCellEditor();
tester.assertNumberOfEventsInCalendar(0);
await tester.tapFilterButtonInGrid('Tags');
await tester.tapOptionFilterWithName('asdf');
await tester.dismissCellEditor();
tester.assertNumberOfEventsInCalendar(1);
await tester.tapFilterButtonInGrid('Tags');
await tester.tapOptionFilterWithName('asdf');
await tester.dismissCellEditor();
tester.assertNumberOfEventsInCalendar(0);
final secondOfThisMonth = DateTime(today.year, today.month, 2);
await tester.doubleClickCalendarCell(secondOfThisMonth);
await tester.dismissEventEditor();
tester.assertNumberOfEventsInCalendar(1);
await tester.openCalendarEvent(index: 0, date: secondOfThisMonth);
await tester.tapButton(finderForFieldType(FieldType.MultiSelect));
await tester.selectOption(name: "asdf");
await tester.dismissCellEditor();
await tester.dismissCellEditor();
tester.assertNumberOfEventsInCalendar(0);
await tester.tapFilterButtonInGrid('Tags');
await tester.changeSelectFilterCondition(
SelectOptionFilterConditionPB.OptionIsEmpty,
);
await tester.dismissCellEditor();
tester.assertNumberOfEventsInCalendar(1);
tester.assertNumberOfEventsOnSpecificDay(1, secondOfThisMonth);
});
});
}
@@ -211,7 +211,7 @@ void main() {
await tester.toggleIncludeTime();
// Select a date
final now = DateTime.now();
DateTime now = DateTime.now();
await tester.selectDay(content: now.day);
await tester.dismissCellEditor();
@@ -225,7 +225,7 @@ void main() {
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
// Toggle include time
// When toggling include time, the time value is from the previous existing date time, not the current time
now = DateTime.now();
await tester.toggleIncludeTime();
await tester.dismissCellEditor();
@@ -15,6 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a database and add a linked database view
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
@@ -29,6 +30,11 @@ void main() {
await tester.tapHidePropertyButton();
tester.noFieldWithName('New field 1');
// create another field, New field 1 to be hidden still
await tester.tapNewPropertyButton();
await tester.dismissFieldEditor();
tester.noFieldWithName('New field 1');
// go back to inline database view, expect field to be shown
await tester.tapTabBarLinkedViewByViewName('Untitled');
tester.findFieldWithName('New field 1');
@@ -60,5 +66,40 @@ void main() {
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1");
});
testWidgets('field cell width', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a database and add a linked database view
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
await tester.tapNewPropertyButton();
await tester.renameField('New field 1');
await tester.dismissFieldEditor();
// check the width of the field
expect(tester.getFieldWidth('New field 1'), 150);
// change the width of the field
await tester.changeFieldWidth('New field 1', 200);
expect(tester.getFieldWidth('New field 1'), 205);
// create another field, New field 1 to be same width
await tester.tapNewPropertyButton();
await tester.dismissFieldEditor();
expect(tester.getFieldWidth('New field 1'), 205);
// go back to inline database view, expect New field 1 to be 150px
await tester.tapTabBarLinkedViewByViewName('Untitled');
expect(tester.getFieldWidth('New field 1'), 150);
// go back to linked database view, expect New field 1 to be 205px
await tester.tapTabBarLinkedViewByViewName('Grid');
expect(tester.getFieldWidth('New field 1'), 205);
});
});
}
@@ -1,8 +1,9 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/widgets/field/type_option_editor/select/select_option.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -13,7 +14,14 @@ import '../../shared/database_test_op.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('grid edit field test:', () {
testWidgets('rename existing field', (tester) async {
@@ -316,6 +324,30 @@ void main() {
);
});
testWidgets('text in viewport while typing', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.changeCalculateAtIndex(0, CalculationType.Count);
// add very large text with 200 lines
final largeText = List.generate(
200,
(index) => 'Line ${index + 1}',
).join('\n');
await tester.editCell(
rowIndex: 2,
fieldType: FieldType.RichText,
input: largeText,
);
// checks if last line is in view port
tester.expectToSeeText('Line 200');
});
// Disable this test because it fails on CI randomly
// testWidgets('last modified and created at field type options',
// (tester) async {
@@ -513,8 +545,8 @@ void main() {
// edit the first date cell
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.DateTime);
await tester.toggleIncludeTime();
final now = DateTime.now();
await tester.toggleIncludeTime();
await tester.selectDay(content: now.day);
await tester.dismissCellEditor();
@@ -557,7 +589,6 @@ void main() {
rowIndex: 0,
fieldType: FieldType.DateTime,
content: DateFormat('MMM dd, y').format(now),
// content: DateFormat('MMM dd, y HH:mm').format(now),
);
tester.assertCellContent(
rowIndex: 1,
@@ -1,7 +1,7 @@
import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -108,7 +108,7 @@ void main() {
await tester.tapOptionFilterWithName('s4');
// The row with 's4' should be shown.
tester.assertNumberOfRowsInGridPage(1);
tester.assertNumberOfRowsInGridPage(2);
await tester.pumpAndSettle();
});
@@ -154,12 +154,10 @@ void main() {
tester.assertNumberOfRowsInGridPage(0);
await tester.tapFilterButtonInGrid('date');
await tester.tapDateFilterButtonInGrid();
await tester.tapDateFilterCondition(DateTimeFilterCondition.before);
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
tester.assertNumberOfRowsInGridPage(7);
await tester.tapDateFilterButtonInGrid();
await tester.tapDateFilterCondition(DateTimeFilterCondition.isEmpty);
await tester.changeDateFilterCondition(DateTimeFilterCondition.isEmpty);
tester.assertNumberOfRowsInGridPage(3);
await tester.pumpAndSettle();
@@ -171,8 +169,6 @@ void main() {
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.scrollToRight(find.byType(GridPage));
await tester.createField(
FieldType.CreatedTime,
name: 'Created at',
@@ -189,11 +185,40 @@ void main() {
tester.assertNumberOfRowsInGridPage(3);
await tester.tapFilterButtonInGrid('Created at');
await tester.tapDateFilterButtonInGrid();
await tester.tapDateFilterCondition(DateTimeFilterCondition.before);
await tester.changeDateFilterCondition(DateTimeFilterCondition.before);
tester.assertNumberOfRowsInGridPage(0);
await tester.pumpAndSettle();
});
testWidgets('create new row when filters don\'t autofill', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(
FieldType.RichText,
'Name',
);
tester.assertNumberOfRowsInGridPage(3);
await tester.tapCreateRowButtonInGrid();
tester.assertNumberOfRowsInGridPage(4);
await tester.tapFilterButtonInGrid('Name');
await tester
.changeTextFilterCondition(TextFilterConditionPB.TextIsNotEmpty);
await tester.dismissCellEditor();
tester.assertNumberOfRowsInGridPage(0);
await tester.tapCreateRowButtonInGrid();
tester.assertNumberOfRowsInGridPage(0);
expect(find.byType(RowDetailPage), findsOneWidget);
await tester.pumpAndSettle();
});
});
}
@@ -0,0 +1,190 @@
import 'dart:convert';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dart';
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart';
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/emoji.dart';
import '../../shared/util.dart';
void main() {
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
testWidgets('change icon', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final iconData = await tester.loadIcon();
const pageName = 'Database';
await tester.createNewPageWithNameUnderParent(
layout: ViewLayoutPB.Grid,
name: pageName,
);
/// create board
final addButton = find.byType(AddDatabaseViewButton);
await tester.tapButton(addButton);
await tester.tapButton(
find.text(
'${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Board.layoutName}',
findRichText: true,
),
);
/// create calendar
await tester.tapButton(addButton);
await tester.tapButton(
find.text(
'${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Calendar.layoutName}',
findRichText: true,
),
);
final databaseTabBarItem = find.byType(DatabaseTabBarItem);
expect(databaseTabBarItem, findsNWidgets(3));
final gridItem = databaseTabBarItem.first,
boardItem = databaseTabBarItem.at(1),
calendarItem = databaseTabBarItem.last;
/// change the icon of grid
/// the first tapping is to select specific item
/// the second tapping is to show the menu
await tester.tapButton(gridItem);
await tester.tapButton(gridItem);
/// change icon
await tester
.tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr()));
await tester.tapIcon(iconData, enableColor: false);
final gridIcon = find.descendant(
of: gridItem,
matching: find.byType(RawEmojiIconWidget),
);
final gridIconWidget =
gridIcon.evaluate().first.widget as RawEmojiIconWidget;
final iconsData = IconsData.fromJson(jsonDecode(iconData.emoji));
final gridIconsData =
IconsData.fromJson(jsonDecode(gridIconWidget.emoji.emoji));
expect(gridIconsData.iconName, iconsData.iconName);
/// change the icon of board
await tester.tapButton(boardItem);
await tester.tapButton(boardItem);
await tester
.tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr()));
await tester.tapIcon(iconData, enableColor: false);
final boardIcon = find.descendant(
of: boardItem,
matching: find.byType(RawEmojiIconWidget),
);
final boardIconWidget =
boardIcon.evaluate().first.widget as RawEmojiIconWidget;
final boardIconsData =
IconsData.fromJson(jsonDecode(boardIconWidget.emoji.emoji));
expect(boardIconsData.iconName, iconsData.iconName);
/// change the icon of calendar
await tester.tapButton(calendarItem);
await tester.tapButton(calendarItem);
await tester
.tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr()));
await tester.tapIcon(iconData, enableColor: false);
final calendarIcon = find.descendant(
of: calendarItem,
matching: find.byType(RawEmojiIconWidget),
);
final calendarIconWidget =
calendarIcon.evaluate().first.widget as RawEmojiIconWidget;
final calendarIconsData =
IconsData.fromJson(jsonDecode(calendarIconWidget.emoji.emoji));
expect(calendarIconsData.iconName, iconsData.iconName);
});
testWidgets('change database icon from sidebar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final iconData = await tester.loadIcon();
final icon = IconsData.fromJson(jsonDecode(iconData.emoji)), emoji = '😄';
const pageName = 'Database';
await tester.createNewPageWithNameUnderParent(
layout: ViewLayoutPB.Grid,
name: pageName,
);
final viewItem = find.descendant(
of: find.byType(SidebarFolder),
matching: find.byWidgetPredicate(
(w) => w is ViewItem && w.view.name == pageName,
),
);
/// change icon to emoji
await tester.tapButton(
find.descendant(
of: viewItem,
matching: find.byType(FlowySvg),
),
);
await tester.tapEmoji(emoji);
final iconWidget = find.descendant(
of: viewItem,
matching: find.byType(RawEmojiIconWidget),
);
expect(
(iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji,
emoji,
);
/// the icon will not be displayed in database item
Finder databaseIcon = find.descendant(
of: find.byType(DatabaseTabBarItem),
matching: find.byType(FlowySvg),
);
expect(
(databaseIcon.evaluate().first.widget as FlowySvg).svg,
FlowySvgs.icon_grid_s,
);
/// change emoji to icon
await tester.tapButton(iconWidget);
await tester.tapIcon(iconData);
expect(
(iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji,
iconData.emoji,
);
databaseIcon = find.descendant(
of: find.byType(DatabaseTabBarItem),
matching: find.byType(RawEmojiIconWidget),
);
final databaseIconWidget =
databaseIcon.evaluate().first.widget as RawEmojiIconWidget;
final databaseIconsData =
IconsData.fromJson(jsonDecode(databaseIconWidget.emoji.emoji));
expect(icon.svgString, databaseIconsData.svgString);
expect(icon.color, isNotEmpty);
expect(icon.color, databaseIconsData.color);
/// the icon in database item should not show the color
expect(databaseIconWidget.enableColor, false);
});
}
@@ -21,7 +21,6 @@ import 'package:path_provider/path_provider.dart';
import '../../shared/database_test_op.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
import '../board/board_hide_groups_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -69,10 +68,7 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
await tester.tapFileUploadHint();
// Expect one file
expect(find.byType(RenderMedia), findsOneWidget);
@@ -85,9 +81,7 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.tapFileUploadHint();
await tester.pumpAndSettle();
// Expect two files
@@ -139,10 +133,7 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
await tester.tapFileUploadHint();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -193,10 +184,7 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
await tester.tapFileUploadHint();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -230,7 +218,7 @@ void main() {
await Future.wait([firstFile.delete(), secondFile.delete()]);
});
testWidgets('hide file names', (tester) async {
testWidgets('show file names', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -272,10 +260,7 @@ void main() {
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
await tester.pumpAndSettle();
await tester.tapFileUploadHint();
// Expect two files
expect(find.byType(RenderMedia), findsNWidgets(2));
@@ -283,28 +268,28 @@ void main() {
await tester.dismissCellEditor();
await tester.pumpAndSettle();
// Open first row in row detail view then toggle hide file names
// Open first row in row detail view then toggle show file names
await tester.openFirstRowDetailPage();
await tester.pumpAndSettle();
// Expect file names to not be shown (hidden)
expect(find.text('sample.jpeg'), findsNothing);
expect(find.text('sample.gif'), findsNothing);
await tester.tapGridFieldWithNameInRowDetailPage('Type');
await tester.pumpAndSettle();
// Toggle show file names
await tester.tap(find.byType(Toggle));
await tester.pumpAndSettle();
// Expect file names to be shown
expect(find.text('sample.jpeg'), findsOneWidget);
expect(find.text('sample.gif'), findsOneWidget);
await tester.tapGridFieldWithNameInRowDetailPage('Type');
await tester.pumpAndSettle();
// Toggle hide file names
await tester.tap(find.byType(Toggle));
await tester.pumpAndSettle();
await tester.dismissRowDetailPage();
await tester.pumpAndSettle();
// Expect file names to be hidden
expect(find.text('sample.jpeg'), findsNothing);
expect(find.text('sample.gif'), findsNothing);
// Remove the temp files
await Future.wait([firstFile.delete(), secondFile.delete()]);
});
@@ -4,12 +4,10 @@ import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/widgets/card/card.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/media_cell_editor.dart';
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/shared/af_image.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
@@ -26,63 +24,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database row cover', () {
testWidgets('add image to media field and check if cover is set (grid)',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
// Invoke the field editor
await tester.tapGridFieldWithName('Type');
await tester.tapEditFieldButton();
// Change to media type
await tester.tapSwitchFieldTypeButton();
await tester.selectFieldType(FieldType.Media);
await tester.dismissFieldEditor();
// Prepare file for upload from local
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final tempDirectory = await getTemporaryDirectory();
final imagePath = p.join(tempDirectory.path, 'sample.jpeg');
final file = File(imagePath)
..writeAsBytesSync(image.buffer.asUint8List());
mockPickFilePaths(paths: [imagePath]);
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
// Open media cell editor
await tester.tapCellInGrid(rowIndex: 0, fieldType: FieldType.Media);
await tester.findMediaCellEditor(findsOneWidget);
// Click on add file button in the Media Cell Editor
await tester.tap(find.text(LocaleKeys.grid_media_addFileOrImage.tr()));
await tester.pumpAndSettle();
// Tap on the upload interaction
await tester.tapButtonWithName(
LocaleKeys.document_plugins_file_fileUploadHint.tr(),
);
// Expect one file
expect(find.byType(RenderMedia), findsOneWidget);
// Close cell editor
await tester.dismissCellEditor();
// Open first row in row detail view
await tester.openFirstRowDetailPage();
await tester.pumpAndSettle();
// Expect a cover to be shown
expect(find.byType(RowCover), findsOneWidget);
// Remove the temp file
await Future.wait([file.delete()]);
});
testWidgets('add and remove cover from Row Detail Card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -1,15 +1,19 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart';
import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -18,7 +22,14 @@ import '../../shared/emoji.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('grid row detail page:', () {
testWidgets('opens', (tester) async {
@@ -73,6 +84,24 @@ void main() {
// The number of emoji should be two. One in the row displayed in the grid
// one in the row detail page.
expect(emojiText, findsNWidgets(2));
// insert a sub page in database
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.pumpAndSettle();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_subPage_name.tr(),
offset: 100,
);
await tester.pumpAndSettle();
// the row detail page should be closed
final rowDetailPage = find.byType(RowDetailPage);
await tester.pumpUntilNotFound(rowDetailPage);
// expect to see a document page
final documentPage = find.byType(DocumentPage);
expect(documentPage, findsOneWidget);
});
testWidgets('remove emoji', (tester) async {
@@ -335,5 +364,145 @@ void main() {
tester.assertNumberOfRowsInGridPage(4);
});
testWidgets('edit checklist cell', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.Checklist;
await tester.createField(fieldType);
await tester.openFirstRowDetailPage();
await tester.hoverOnWidget(
find.byType(ChecklistRowDetailCell),
onHover: () async {
await tester.tapButton(find.byType(ChecklistItemControl));
},
);
tester.assertPhantomChecklistItemAtIndex(index: 0);
await tester.enterText(find.byType(PhantomChecklistItem), 'task 1');
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
tester.assertChecklistTaskInEditor(
index: 0,
name: "task 1",
isChecked: false,
);
tester.assertPhantomChecklistItemAtIndex(index: 1);
tester.assertPhantomChecklistItemContent("");
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
await tester.pumpAndSettle();
await tester.hoverOnWidget(
find.byType(ChecklistRowDetailCell),
onHover: () async {
await tester.tapButton(find.byType(ChecklistItemControl));
},
);
tester.assertChecklistTaskInEditor(
index: 1,
name: "task 2",
isChecked: false,
);
tester.assertPhantomChecklistItemAtIndex(index: 2);
tester.assertPhantomChecklistItemContent("");
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
expect(find.byType(PhantomChecklistItem), findsNothing);
await tester.renameChecklistTask(index: 0, name: "task -1", enter: false);
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
tester.assertChecklistTaskInEditor(
index: 0,
name: "task -1",
isChecked: false,
);
tester.assertPhantomChecklistItemAtIndex(index: 1);
await tester.enterText(find.byType(PhantomChecklistItem), 'task 0');
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
tester.assertPhantomChecklistItemAtIndex(index: 2);
await tester.checkChecklistTask(index: 1);
expect(find.byType(PhantomChecklistItem), findsNothing);
expect(find.byType(ChecklistItem), findsNWidgets(3));
tester.assertChecklistTaskInEditor(
index: 0,
name: "task -1",
isChecked: false,
);
tester.assertChecklistTaskInEditor(
index: 1,
name: "task 0",
isChecked: true,
);
tester.assertChecklistTaskInEditor(
index: 2,
name: "task 2",
isChecked: false,
);
await tester.tapButton(
find.descendant(
of: find.byType(ProgressAndHideCompleteButton),
matching: find.byType(FlowyIconButton),
),
);
expect(find.byType(ChecklistItem), findsNWidgets(2));
tester.assertChecklistTaskInEditor(
index: 0,
name: "task -1",
isChecked: false,
);
tester.assertChecklistTaskInEditor(
index: 1,
name: "task 2",
isChecked: false,
);
await tester.renameChecklistTask(index: 1, name: "task 3", enter: false);
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.renameChecklistTask(index: 0, name: "task 1", enter: false);
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.enterText(find.byType(PhantomChecklistItem), 'task 2');
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
tester.assertChecklistTaskInEditor(
index: 0,
name: "task 1",
isChecked: false,
);
tester.assertChecklistTaskInEditor(
index: 1,
name: "task 2",
isChecked: false,
);
tester.assertChecklistTaskInEditor(
index: 2,
name: "task 3",
isChecked: false,
);
tester.assertPhantomChecklistItemAtIndex(index: 2);
await tester.checkChecklistTask(index: 1);
expect(find.byType(ChecklistItem), findsNWidgets(2));
});
});
}
@@ -0,0 +1,20 @@
import 'package:integration_test/integration_test.dart';
import 'database_cell_test.dart' as database_cell_test;
import 'database_field_settings_test.dart' as database_field_settings_test;
import 'database_field_test.dart' as database_field_test;
import 'database_row_page_test.dart' as database_row_page_test;
import 'database_setting_test.dart' as database_setting_test;
import 'database_share_test.dart' as database_share_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
database_cell_test.main();
database_field_test.main();
database_field_settings_test.main();
database_share_test.main();
database_row_page_test.main();
database_setting_test.main();
// DON'T add more tests here.
}
@@ -0,0 +1,22 @@
import 'package:integration_test/integration_test.dart';
import 'database_calendar_test.dart' as database_calendar_test;
import 'database_filter_test.dart' as database_filter_test;
import 'database_media_test.dart' as database_media_test;
import 'database_row_cover_test.dart' as database_row_cover_test;
import 'database_share_test.dart' as database_share_test;
import 'database_sort_test.dart' as database_sort_test;
import 'database_view_test.dart' as database_view_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
database_filter_test.main();
database_sort_test.main();
database_view_test.main();
database_calendar_test.main();
database_media_test.main();
database_row_cover_test.main();
database_share_test.main();
// DON'T add more tests here.
}
@@ -1,5 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -73,5 +78,37 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('insert grid in column', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
/// create page and show slash menu
await tester.createNewPageWithNameUnderParent(name: 'test page');
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.pumpAndSettle();
/// create a column
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_twoColumns.tr(),
);
final actionList = find.byType(BlockActionList);
expect(actionList, findsNWidgets(2));
final position = tester.getCenter(actionList.last);
/// tap the second child of column
await tester.tapAt(position.copyWith(dx: position.dx + 50));
/// create a grid
await tester.editor.showSlashMenu();
await tester.pumpAndSettle();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_grid.tr(),
);
final grid = find.byType(GridPageContent);
expect(grid, findsOneWidget);
});
});
}
@@ -27,8 +27,9 @@ void main() {
await tester.pumpAndSettle();
// click the align center
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
await tester
.tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_center_m);
// expect to see the align center
final editorState = tester.editor.getCurrentEditorState();
@@ -36,13 +37,15 @@ void main() {
expect(first.attributes[blockComponentAlign], 'center');
// click the align right
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
await tester
.tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_right_m);
expect(first.attributes[blockComponentAlign], 'right');
// click the align left
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
await tester
.tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_left_m);
expect(first.attributes[blockComponentAlign], 'left');
});
@@ -75,7 +78,7 @@ void main() {
[
LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
LogicalKeyboardKey.keyE,
LogicalKeyboardKey.keyC,
],
tester: tester,
withKeyUp: true,
@@ -0,0 +1,47 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Block option interaction tests', () {
testWidgets('has correct block selection on tap option button',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// We edit the document by entering some characters, to ensure the document has focus
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [2])),
);
// Insert character 'a' three times - easy to identify
await tester.ime.insertText('aaa');
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
final node = editorState.getNodeAtPath([2]);
expect(node?.delta?.toPlainText(), startsWith('aaa'));
final multiSelection = Selection(
start: Position(path: [2], offset: 3),
end: Position(path: [4], offset: 40),
);
// Select multiple items
await tester.editor.updateSelection(multiSelection);
await tester.pumpAndSettle();
// Press the block option menu
await tester.editor.hoverAndClickOptionMenuButton([2]);
await tester.pumpAndSettle();
// Expect the selection to be Block type and not have changed
expect(editorState.selectionType, SelectionType.block);
expect(editorState.selection, multiSelection);
});
});
}
@@ -0,0 +1,67 @@
import 'dart:convert';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/base/icon/icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/emoji.dart';
import '../../shared/util.dart';
void main() {
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
testWidgets('callout with emoji icon picker', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final emojiIconData = await tester.loadIcon();
/// create a new document
await tester.createNewPageWithNameUnderParent();
/// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
/// create callout
await tester.editor.showSlashMenu();
await tester.pumpAndSettle();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_callout.tr(),
);
/// select an icon
final emojiPickerButton = find.descendant(
of: find.byType(CalloutBlockComponentWidget),
matching: find.byType(EmojiPickerButton),
);
await tester.tapButton(emojiPickerButton);
await tester.tapIcon(emojiIconData);
/// verification results
final iconData = IconsData.fromJson(jsonDecode(emojiIconData.emoji));
final iconWidget = find
.descendant(
of: emojiPickerButton,
matching: find.byType(IconWidget),
)
.evaluate()
.first
.widget as IconWidget;
final iconWidgetData = iconWidget.iconsData;
expect(iconWidgetData.svgString, iconData.svgString);
expect(iconWidgetData.iconName, iconData.iconName);
expect(iconWidgetData.groupName, iconData.groupName);
});
}
@@ -1,16 +1,17 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:universal_platform/universal_platform.dart';
@@ -20,17 +21,20 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('copy and paste in document', () {
group('copy and paste in document:', () {
testWidgets('paste multiple lines at the first line', (tester) async {
// mock the clipboard
const lines = 3;
await tester.pasteContent(
plainText: List.generate(lines, (index) => 'line $index').join('\n'),
(editorState) {
expect(editorState.document.root.children.length, 3);
expect(editorState.document.root.children.length, 1);
final text =
editorState.document.root.children.first.delta!.toPlainText();
final textLines = text.split('\n');
for (var i = 0; i < lines; i++) {
expect(
editorState.getNodeAtPath([i])!.delta!.toPlainText(),
textLines[i],
'line $i',
);
}
@@ -170,319 +174,370 @@ void main() {
},
);
});
});
testWidgets('paste text on part of bullet list', (tester) async {
const plainText = 'test';
testWidgets('paste text on part of bullet list', (tester) async {
const plainText = 'test';
await tester.pasteContent(
plainText: plainText,
beforeTest: (editorState) async {
final transaction = editorState.transaction;
transaction.insertNodes(
[0],
[
Node(
type: BulletedListBlockKeys.type,
attributes: {
'delta': [
{"insert": "bullet list"},
],
},
),
],
);
// Set the selection to the second numbered list node (which has empty delta)
transaction.afterSelection = Selection(
start: Position(path: [0], offset: 7),
end: Position(path: [0], offset: 11),
);
await editorState.apply(transaction);
await tester.pumpAndSettle();
},
(editorState) {
final node = editorState.getNodeAtPath([0]);
expect(node?.delta?.toPlainText(), 'bullet test');
expect(node?.type, BulletedListBlockKeys.type);
},
);
});
testWidgets('paste image(png) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.png');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('png', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets('paste image(jpeg) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('jpeg', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets('paste image(gif) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.gif');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('gif', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets(
'format the selected text to href when pasting url if available',
(tester) async {
const text = 'appflowy';
const url = 'https://appflowy.io';
await tester.pasteContent(
plainText: url,
plainText: plainText,
beforeTest: (editorState) async {
await tester.ime.insertText(text);
await tester.editor.updateSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: text.length,
),
final transaction = editorState.transaction;
transaction.insertNodes(
[0],
[
Node(
type: BulletedListBlockKeys.type,
attributes: {
'delta': [
{"insert": "bullet list"},
],
},
),
],
);
},
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{
'insert': text,
'attributes': {'href': url},
}
]);
},
);
},
);
// https://github.com/AppFlowy-IO/AppFlowy/issues/3263
testWidgets(
'paste the image from clipboard when html and image are both available',
(tester) async {
const html =
'''<meta charset='utf-8'><img src="https://user-images.githubusercontent.com/9403740/262918875-603f4adb-58dd-49b5-8201-341d354935fd.png" alt="image"/>''';
final image = await rootBundle.load('assets/test/images/sample.png');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(
html: html,
image: ('png', bytes),
(editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
},
);
},
);
// Set the selection to the second numbered list node (which has empty delta)
transaction.afterSelection = Selection(
start: Position(path: [0], offset: 7),
end: Position(path: [0], offset: 11),
);
testWidgets('paste the html content contains section', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">AppFlowy</strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">Hello World</strong></span></section>''';
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 2);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
expect(node1.type, ParagraphBlockKeys.type);
expect(node2.type, ParagraphBlockKeys.type);
});
});
testWidgets('paste the html from google translation', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">new force</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">Assessment focus: potential motivations, empathy</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-Line truncated
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 8);
});
});
testWidgets(
'auto convert url to link preview block',
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
// the second one is the paragraph node
expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
// hover on the link preview block
// click the more button
// and select convert to link
await tester.hoverOnWidget(
find.byType(CustomLinkPreviewWidget),
onHover: () async {
final convertToLinkButton = find.byWidgetPredicate((widget) {
return widget is MenuBlockButton &&
widget.tooltip ==
LocaleKeys.document_plugins_urlPreview_convertToLink.tr();
});
expect(convertToLinkButton, findsOneWidget);
await tester.tap(convertToLinkButton);
await editorState.apply(transaction);
await tester.pumpAndSettle();
},
);
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
final textNode = editorState.getNodeAtPath([0])!;
expect(textNode.type, ParagraphBlockKeys.type);
expect(textNode.delta!.toJson(), [
{
'insert': url,
'attributes': {'href': url},
}
]);
},
);
testWidgets(
'ctrl/cmd+z to undo the auto convert url to link preview block',
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
// the second one is the paragraph node
expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
await tester.editor.tapLineOfEditorAt(0);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{
'insert': url,
'attributes': {'href': url},
}
]);
},
);
testWidgets(
'paste the nodes start with non-delta node',
(tester) async {
await tester.pasteContent((_) {});
const text = 'Hello World';
final editorState = tester.editor.getCurrentEditorState();
final transaction = editorState.transaction;
// [image_block]
// [paragraph_block]
transaction.insertNodes([
0,
], [
customImageNode(url: ''),
paragraphNode(text: text),
]);
await editorState.apply(transaction);
await tester.pumpAndSettle();
await tester.editor.tapLineOfEditorAt(0);
// select all and copy
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyA,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyC,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
// put the cursor to the end of the paragraph block
await tester.editor.tapLineOfEditorAt(0);
// paste the content
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
// expect the image and the paragraph block are inserted below the cursor
expect(editorState.getNodeAtPath([0])!.type, CustomImageBlockKeys.type);
expect(editorState.getNodeAtPath([1])!.type, ParagraphBlockKeys.type);
expect(editorState.getNodeAtPath([2])!.type, CustomImageBlockKeys.type);
expect(editorState.getNodeAtPath([3])!.type, ParagraphBlockKeys.type);
},
);
testWidgets('paste the url without protocol', (tester) async {
// paste the image that from local file
const plainText = '1.jpg';
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
final node = editorState.getNodeAtPath([0]);
expect(node?.delta?.toPlainText(), 'bullet test');
expect(node?.type, BulletedListBlockKeys.type);
},
);
});
});
testWidgets('paste the image url', (tester) async {
const plainText = 'https://appflowy.io/1.jpg';
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
testWidgets('paste image(png) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.png');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('png', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets('paste image(jpeg) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('jpeg', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets('paste image(gif) from memory', (tester) async {
final image = await rootBundle.load('assets/test/images/sample.gif');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(image: ('gif', bytes), (editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotNull);
});
});
testWidgets(
'format the selected text to href when pasting url if available',
(tester) async {
const text = 'appflowy';
const url = 'https://appflowy.io';
await tester.pasteContent(
plainText: url,
beforeTest: (editorState) async {
await tester.ime.insertText(text);
await tester.editor.updateSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: text.length,
),
);
},
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{
'insert': text,
'attributes': {'href': url},
}
]);
},
);
},
);
// https://github.com/AppFlowy-IO/AppFlowy/issues/3263
testWidgets(
'paste the image from clipboard when html and image are both available',
(tester) async {
const html =
'''<meta charset='utf-8'><img src="https://user-images.githubusercontent.com/9403740/262918875-603f4adb-58dd-49b5-8201-341d354935fd.png" alt="image"/>''';
final image = await rootBundle.load('assets/test/images/sample.png');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(
html: html,
image: ('png', bytes),
(editorState) {
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
},
);
},
);
testWidgets('paste the html content contains section', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">AppFlowy</strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;">Hello World</strong></span></section>''';
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 2);
final node1 = editorState.getNodeAtPath([0])!;
final node2 = editorState.getNodeAtPath([1])!;
expect(node1.type, ParagraphBlockKeys.type);
expect(node2.type, ParagraphBlockKeys.type);
});
});
testWidgets('paste the html from google translation', (tester) async {
const html =
'''<meta charset='utf-8'><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">new force</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><span style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgb(0, 160, 113);"><strong style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; vertical-align: inherit;">Assessment focus: potential motivations, empathy</font></font></strong></span></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><br style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;"></section><section style="margin: 0px 8px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box; overflow-wrap: break-word !important; color: rgba(255, 255, 255, 0.6); font-family: system-ui, -apple-system, &quot;system-ui&quot;, &quot;Helvetica Neue&quot;, &quot;PingFang SC&quot;, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei UI&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 15px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: 0.544px; orphans: 2; text-align: justify; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; background-color: rgb(25, 25, 25); text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;"><font style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: bordeLine truncated
await tester.pasteContent(html: html, (editorState) {
expect(editorState.document.root.children.length, 8);
});
});
testWidgets(
'auto convert url to link preview block',
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
final pasteAsMenu = find.byType(PasteAsMenu);
expect(pasteAsMenu, findsOneWidget);
final bookmarkButton = find.text(
LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(),
);
await tester.tapButton(bookmarkButton);
// the second one is the paragraph node
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
// hover on the link preview block
// click the more button
// and select convert to link
await tester.hoverOnWidget(
find.byType(CustomLinkPreviewWidget),
onHover: () async {
/// show menu
final menu = find.byType(CustomLinkPreviewMenu);
expect(menu, findsOneWidget);
await tester.tapButton(menu);
final convertToLinkButton = find.text(
LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl
.tr(),
);
expect(convertToLinkButton, findsOneWidget);
await tester.tapButton(convertToLinkButton);
},
);
final editorState = tester.editor.getCurrentEditorState();
final textNode = editorState.getNodeAtPath([0])!;
expect(textNode.type, ParagraphBlockKeys.type);
expect(textNode.delta!.toJson(), [
{
'insert': url,
'attributes': {'href': url},
}
]);
},
);
testWidgets(
'ctrl/cmd+z to undo the auto convert url to link preview block',
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
final pasteAsMenu = find.byType(PasteAsMenu);
expect(pasteAsMenu, findsOneWidget);
final bookmarkButton = find.text(
LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(),
);
await tester.tapButton(bookmarkButton);
// the second one is the paragraph node
expect(editorState.document.root.children.length, 1);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{
'insert': url,
'attributes': {'href': url},
}
]);
},
);
testWidgets(
'paste the nodes start with non-delta node',
(tester) async {
await tester.pasteContent((_) {});
const text = 'Hello World';
final editorState = tester.editor.getCurrentEditorState();
final transaction = editorState.transaction;
// [image_block]
// [paragraph_block]
transaction.insertNodes([
0,
], [
customImageNode(url: ''),
paragraphNode(text: text),
]);
await editorState.apply(transaction);
await tester.pumpAndSettle();
await tester.editor.tapLineOfEditorAt(0);
// select all and copy
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyA,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyC,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
// put the cursor to the end of the paragraph block
await tester.editor.tapLineOfEditorAt(0);
// paste the content
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
// expect the image and the paragraph block are inserted below the cursor
expect(editorState.getNodeAtPath([0])!.type, CustomImageBlockKeys.type);
expect(editorState.getNodeAtPath([1])!.type, ParagraphBlockKeys.type);
expect(editorState.getNodeAtPath([2])!.type, CustomImageBlockKeys.type);
expect(editorState.getNodeAtPath([3])!.type, ParagraphBlockKeys.type);
},
);
testWidgets('paste the url without protocol', (tester) async {
// paste the image that from local file
const plainText = '1.jpg';
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
});
});
testWidgets('paste the image url', (tester) async {
const plainText = 'http://example.com/1.jpg';
final image = await rootBundle.load('assets/test/images/sample.jpeg');
final bytes = image.buffer.asUint8List();
await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes),
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
});
});
const testMarkdownText = '''
# I'm h1
## I'm h2
### I'm h3
#### I'm h4
##### I'm h5
###### I'm h6''';
testWidgets('paste markdowns', (tester) async {
await tester.pasteContent(
plainText: testMarkdownText,
(editorState) {
final node = editorState.getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
final children = editorState.document.root.children;
expect(children.length, 6);
for (int i = 1; i <= children.length; i++) {
final text = children[i - 1].delta!.toPlainText();
expect(text, 'I\'m h$i');
}
},
);
});
testWidgets('paste markdowns as plain', (tester) async {
await tester.pasteContent(
plainText: testMarkdownText,
pasteAsPlain: true,
(editorState) {
final children = editorState.document.root.children;
expect(children.length, 6);
for (int i = 1; i <= children.length; i++) {
final text = children[i - 1].delta!.toPlainText();
final expectText = '${'#' * i} I\'m h$i';
expect(text, expectText);
}
},
);
});
});
}
extension on WidgetTester {
Future<void> pasteContent(
void Function(EditorState editorState) test, {
FutureOr<void> Function(EditorState editorState) test, {
Future<void> Function(EditorState editorState)? beforeTest,
String? plainText,
String? html,
String? inAppJson,
bool pasteAsPlain = false,
(String, Uint8List?)? image,
}) async {
await initializeAppFlowy();
await tapAnonymousSignInButton();
// create a new document
await createNewPageWithNameUnderParent(name: 'Test Document');
await createNewPageWithNameUnderParent();
// tap the editor
await tapButton(find.byType(AppFlowyEditor));
@@ -502,10 +557,11 @@ extension on WidgetTester {
await simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isShiftPressed: pasteAsPlain,
isMetaPressed: Platform.isMacOS,
);
await pumpAndSettle();
await pumpAndSettle(const Duration(milliseconds: 1000));
test(editor.getCurrentEditorState());
await test(editor.getCurrentEditorState());
}
}
@@ -13,6 +13,8 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final finder = find.text(gettingStarted, findRichText: true);
await tester.pumpUntilFound(finder, timeout: const Duration(seconds: 2));
// create a new document
const pageName = 'Test Document';
@@ -0,0 +1,84 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
import 'document_inline_page_reference_test.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Document deletion', () {
testWidgets('Trash breadcrumb', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// This test shares behavior with the inline page reference test, thus
// we utilize the same helper functions there.
final name = await createDocumentToReference(tester);
await tester.editor.tapLineOfEditorAt(0);
await tester.pumpAndSettle();
await triggerReferenceDocumentBySlashMenu(tester);
// Search for prefix of document
await enterDocumentText(tester);
// Select result
final optionFinder = find.descendant(
of: find.byType(InlineActionsHandler),
matching: find.text(name),
);
await tester.tap(optionFinder);
await tester.pumpAndSettle();
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
// Delete the page
await tester.hoverOnPageName(
name,
onHover: () async => tester.tapDeletePageButton(),
);
await tester.pumpAndSettle();
// Navigate to the deleted page from the inline mention
await tester.tap(mentionBlock);
await tester.pumpUntilFound(find.byType(TrashBreadcrumb));
expect(find.byType(TrashBreadcrumb), findsOneWidget);
// Navigate using the trash breadcrumb
await tester.tap(
find.descendant(
of: find.byType(TrashBreadcrumb),
matching: find.text(
LocaleKeys.trash_text.tr(),
),
),
);
await tester.pumpUntilFound(find.text(LocaleKeys.trash_restoreAll.tr()));
// Restore all
await tester.tap(find.text(LocaleKeys.trash_restoreAll.tr()));
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.trash_restore.tr()));
await tester.pumpAndSettle();
// Navigate back to the document
await tester.openPage('Getting started');
await tester.pumpAndSettle();
await tester.tap(mentionBlock);
await tester.pumpAndSettle();
expect(find.byType(TrashBreadcrumb), findsNothing);
});
});
}
@@ -0,0 +1,160 @@
import 'dart:math';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:universal_platform/universal_platform.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
String generateRandomString(int len) {
final r = Random();
return String.fromCharCodes(
List.generate(len, (index) => r.nextInt(33) + 89),
);
}
testWidgets(
'document find menu test',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent();
// tap editor to get focus
await tester.tapButton(find.byType(AppFlowyEditor));
// set clipboard data
final data = [
"123456\n\n",
...List.generate(100, (_) => "${generateRandomString(50)}\n\n"),
"1234567\n\n",
...List.generate(100, (_) => "${generateRandomString(50)}\n\n"),
"12345678\n\n",
...List.generate(100, (_) => "${generateRandomString(50)}\n\n"),
].join();
await getIt<ClipboardService>().setData(
ClipboardServiceData(
plainText: data,
),
);
// paste
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
// go back to beginning of document
// FIXME: Cannot run Ctrl+F unless selection is on screen
await tester.editor
.updateSelection(Selection.collapsed(Position(path: [0])));
await tester.pumpAndSettle();
expect(find.byType(FindAndReplaceMenuWidget), findsNothing);
// press cmd/ctrl+F to display the find menu
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyF,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(FindAndReplaceMenuWidget), findsOneWidget);
final textField = find.descendant(
of: find.byType(FindAndReplaceMenuWidget),
matching: find.byType(TextField),
);
await tester.enterText(
textField,
"123456",
);
await tester.pumpAndSettle();
await tester.pumpAndSettle();
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.text("123456", findRichText: true),
),
findsOneWidget,
);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.pumpAndSettle();
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.text("1234567", findRichText: true),
),
findsOneWidget,
);
await tester.showKeyboard(textField);
await tester.idle();
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
await tester.pumpAndSettle();
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.text("12345678", findRichText: true),
),
findsOneWidget,
);
// tap next button, go back to beginning of document
await tester.tapButton(
find.descendant(
of: find.byType(FindMenu),
matching: find.byFlowySvg(FlowySvgs.arrow_down_s),
),
);
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.text("123456", findRichText: true),
),
findsOneWidget,
);
/// press cmd/ctrl+F to display the find menu
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyF,
isControlPressed:
UniversalPlatform.isLinux || UniversalPlatform.isWindows,
isMetaPressed: UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(FindAndReplaceMenuWidget), findsOneWidget);
/// press esc to dismiss the find menu
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
expect(find.byType(FindAndReplaceMenuWidget), findsNothing);
},
);
}
@@ -0,0 +1,382 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/keyboard.dart';
import '../../shared/util.dart';
const _firstDocName = "Inline Sub Page Mention";
const _createdPageName = "hi world";
// Test cases that are covered in this file:
// - [x] Insert sub page mention from action menu (+)
// - [x] Delete sub page mention from editor
// - [x] Delete page from sidebar
// - [x] Delete page from sidebar and then trash
// - [x] Undo delete sub page mention
// - [x] Cut+paste in same document
// - [x] Cut+paste in different document
// - [x] Cut+paste in same document and then paste again in same document
// - [x] Turn paragraph with sub page mention into a heading
// - [x] Turn heading with sub page mention into a paragraph
// - [x] Duplicate a Block containing two sub page mentions
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document inline sub-page mention tests:', () {
testWidgets('Insert (& delete) a sub page mention from action menu',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.insertInlineSubPageFromPlusMenu();
await tester.expandOrCollapsePage(
pageName: _firstDocName,
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Delete from editor
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNothing);
expect(find.byType(MentionSubPageBlock), findsNothing);
// Undo
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Move to trash (delete from sidebar)
await tester.rightClickOnPageName(_createdPageName);
await tester.tapButtonWithName(ViewMoreActionType.delete.name);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsOneWidget);
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(
find.text(LocaleKeys.document_mention_trashHint.tr()),
findsOneWidget,
);
// Delete from trash
await tester.tapTrashButton();
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.trash_deleteAll.tr()));
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.button_delete.tr()));
await tester.pumpAndSettle();
await tester.openPage(_firstDocName);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNothing);
expect(find.byType(MentionSubPageBlock), findsOneWidget);
expect(
find.text(LocaleKeys.document_mention_deletedPage.tr()),
findsOneWidget,
);
});
testWidgets(
'Cut+paste in same document and cut+paste in different document',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.insertInlineSubPageFromPlusMenu();
await tester.expandOrCollapsePage(
pageName: _firstDocName,
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Cut from editor
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyX,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNothing);
expect(find.byType(MentionSubPageBlock), findsNothing);
// Paste in same document
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Cut again
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyX,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
// Create another document
const anotherDocName = "Another Document";
await tester.createOpenRenameDocumentUnderParent(
name: anotherDocName,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNothing);
expect(find.byType(MentionSubPageBlock), findsNothing);
await tester.editor.tapLineOfEditorAt(0);
await tester.pumpAndSettle();
// Paste in document
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpUntilFound(find.byType(MentionSubPageBlock));
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsOneWidget);
await tester.expandOrCollapsePage(
pageName: anotherDocName,
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
});
testWidgets(
'Cut+paste in same docuemnt and then paste again in same document',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.insertInlineSubPageFromPlusMenu();
await tester.expandOrCollapsePage(
pageName: _firstDocName,
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Cut from editor
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyX,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNothing);
expect(find.byType(MentionSubPageBlock), findsNothing);
// Paste in same document
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Paste again
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle(const Duration(seconds: 2));
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
expect(find.text('$_createdPageName (copy)'), findsNWidgets(2));
});
testWidgets('Turn into w/ sub page mentions', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.insertInlineSubPageFromPlusMenu();
await tester.expandOrCollapsePage(
pageName: _firstDocName,
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
final headingText = LocaleKeys.document_slashMenu_name_heading1.tr();
final paragraphText = LocaleKeys.document_slashMenu_name_text.tr();
// Turn into heading
await tester.editor.openTurnIntoMenu([0]);
await tester.tapButton(find.findTextInFlowyText(headingText));
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
// Turn into paragraph
await tester.editor.openTurnIntoMenu([0]);
await tester.tapButton(find.findTextInFlowyText(paragraphText));
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsNWidgets(2));
expect(find.byType(MentionSubPageBlock), findsOneWidget);
});
testWidgets('Duplicate a block containing two sub page mentions',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.insertInlineSubPageFromPlusMenu();
// Copy paste it
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: 1),
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyC,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsOneWidget);
expect(find.text("$_createdPageName (copy)"), findsOneWidget);
expect(find.byType(MentionSubPageBlock), findsNWidgets(2));
// Duplicate node from block action menu
await tester.editor.hoverAndClickOptionMenuButton([0]);
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
await tester.pumpAndSettle();
expect(find.text(_createdPageName), findsOneWidget);
expect(find.text("$_createdPageName (copy)"), findsNWidgets(2));
expect(find.text("$_createdPageName (copy) (copy)"), findsOneWidget);
});
testWidgets('Cancel inline page reference menu by space', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createOpenRenameDocumentUnderParent(name: _firstDocName);
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showPlusMenu();
// Cancel by space
await tester.simulateKeyEvent(
LogicalKeyboardKey.space,
);
await tester.pumpAndSettle();
expect(find.byType(InlineActionsMenu), findsNothing);
});
});
}
extension _InlineSubPageTestHelper on WidgetTester {
Future<void> insertInlineSubPageFromPlusMenu() async {
await editor.tapLineOfEditorAt(0);
await editor.showPlusMenu();
// Workaround to allow typing a document name
await FlowyTestKeyboard.simulateKeyDownEvent(
tester: this,
withKeyUp: true,
[
LogicalKeyboardKey.keyH,
LogicalKeyboardKey.keyI,
LogicalKeyboardKey.space,
LogicalKeyboardKey.keyW,
LogicalKeyboardKey.keyO,
LogicalKeyboardKey.keyR,
LogicalKeyboardKey.keyL,
LogicalKeyboardKey.keyD,
],
);
await FlowyTestKeyboard.simulateKeyDownEvent(
tester: this,
withKeyUp: true,
[LogicalKeyboardKey.enter],
);
await pumpUntilFound(find.byType(MentionSubPageBlock));
}
}
@@ -0,0 +1,453 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
const avaliableLink = 'https://appflowy.io/',
unavailableLink = 'www.thereIsNoting.com';
Future<void> preparePage(WidgetTester tester, {String? pageName}) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: pageName);
await tester.editor.tapLineOfEditorAt(0);
}
Future<void> pasteLink(WidgetTester tester, String link) async {
await getIt<ClipboardService>()
.setData(ClipboardServiceData(plainText: link));
/// paste the link
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle(Duration(seconds: 1));
}
Future<void> pasteAs(
WidgetTester tester,
String link,
PasteMenuType type, {
Duration waitTime = const Duration(milliseconds: 500),
}) async {
await pasteLink(tester, link);
final convertToMentionButton = find.text(type.title);
await tester.tapButton(convertToMentionButton);
await tester.pumpAndSettle(waitTime);
}
void checkUrl(Node node, String link) {
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{
'insert': link,
'attributes': {'href': link},
}
]);
}
void checkMention(Node node, String link) {
final delta = node.delta!;
final insert = (delta.first as TextInsert).text;
final attributes = delta.first.attributes;
expect(insert, MentionBlockKeys.mentionChar);
final mention =
attributes?[MentionBlockKeys.mention] as Map<String, dynamic>;
expect(mention[MentionBlockKeys.type], MentionType.externalLink.name);
expect(mention[MentionBlockKeys.url], avaliableLink);
}
void checkBookmark(Node node, String link) {
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], link);
}
void checkEmbed(Node node, String link) {
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkEmbedKeys.previewType], LinkEmbedKeys.embed);
expect(node.attributes[LinkPreviewBlockKeys.url], link);
}
group('Paste as URL', () {
Future<void> pasteAndTurnInto(
WidgetTester tester,
String link,
String title,
) async {
await pasteLink(tester, link);
final convertToLinkButton = find
.text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr());
await tester.tapButton(convertToLinkButton);
/// hover link and turn into mention
await tester.hoverOnWidget(
find.byType(LinkHoverTrigger),
onHover: () async {
final turnintoButton = find.byFlowySvg(FlowySvgs.turninto_m);
await tester.tapButton(turnintoButton);
final convertToButton = find.text(title);
await tester.tapButton(convertToButton);
await tester.pumpAndSettle(Duration(seconds: 1));
},
);
}
testWidgets('paste a link', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteLink(tester, link);
final convertToLinkButton = find
.text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr());
await tester.tapButton(convertToLinkButton);
final node = tester.editor.getNodeAtPath([0]);
checkUrl(node, link);
});
testWidgets('paste a link and turn into mention', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAndTurnInto(
tester,
link,
LinkConvertMenuCommand.toMention.title,
);
/// check metion values
final node = tester.editor.getNodeAtPath([0]);
checkMention(node, link);
});
testWidgets('paste a link and turn into bookmark', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAndTurnInto(
tester,
link,
LinkConvertMenuCommand.toBookmark.title,
);
/// check metion values
final node = tester.editor.getNodeAtPath([0]);
checkBookmark(node, link);
});
testWidgets('paste a link and turn into embed', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAndTurnInto(
tester,
link,
LinkConvertMenuCommand.toEmbed.title,
);
/// check metion values
final node = tester.editor.getNodeAtPath([0]);
checkEmbed(node, link);
});
});
group('Paste as Mention', () {
Future<void> pasteAsMention(WidgetTester tester, String link) =>
pasteAs(tester, link, PasteMenuType.mention);
String getMentionLink(Node node) {
final insert = node.delta?.first as TextInsert?;
final mention = insert?.attributes?[MentionBlockKeys.mention]
as Map<String, dynamic>?;
return mention?[MentionBlockKeys.url] ?? '';
}
Future<void> hoverMentionAndClick(
WidgetTester tester,
String command,
) async {
final mentionLink = find.byType(MentionLinkBlock);
expect(mentionLink, findsOneWidget);
await tester.hoverOnWidget(
mentionLink,
onHover: () async {
final errorPreview = find.byType(MentionLinkErrorPreview);
expect(errorPreview, findsOneWidget);
final convertButton = find.byFlowySvg(FlowySvgs.turninto_m);
await tester.tapButton(convertButton);
final menuButton = find.text(command);
await tester.tapButton(menuButton);
},
);
}
testWidgets('paste a link as mention', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
final node = tester.editor.getNodeAtPath([0]);
checkMention(node, link);
});
testWidgets('paste as mention and copy link', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
final mentionLink = find.byType(MentionLinkBlock);
expect(mentionLink, findsOneWidget);
await tester.hoverOnWidget(
mentionLink,
onHover: () async {
final preview = find.byType(MentionLinkPreview);
if (!preview.hasFound) {
final copyButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
await tester.tapButton(copyButton);
} else {
final moreOptionButton = find.byFlowySvg(FlowySvgs.toolbar_more_m);
await tester.tapButton(moreOptionButton);
final copyButton =
find.text(MentionLinktMenuCommand.copyLink.title);
await tester.tapButton(copyButton);
}
},
);
final clipboardContent = await getIt<ClipboardService>().getData();
expect(clipboardContent.plainText, link);
});
testWidgets('paste as error mention and turninto url', (tester) async {
String link = unavailableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
Node node = tester.editor.getNodeAtPath([0]);
link = getMentionLink(node);
await hoverMentionAndClick(
tester,
MentionLinktErrorMenuCommand.toURL.title,
);
node = tester.editor.getNodeAtPath([0]);
checkUrl(node, link);
});
testWidgets('paste as error mention and turninto embed', (tester) async {
String link = unavailableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
Node node = tester.editor.getNodeAtPath([0]);
link = getMentionLink(node);
await hoverMentionAndClick(
tester,
MentionLinktErrorMenuCommand.toEmbed.title,
);
node = tester.editor.getNodeAtPath([0]);
checkEmbed(node, link);
});
testWidgets('paste as error mention and turninto bookmark', (tester) async {
String link = unavailableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
Node node = tester.editor.getNodeAtPath([0]);
link = getMentionLink(node);
await hoverMentionAndClick(
tester,
MentionLinktErrorMenuCommand.toBookmark.title,
);
node = tester.editor.getNodeAtPath([0]);
checkBookmark(node, link);
});
testWidgets('paste as error mention and remove link', (tester) async {
String link = unavailableLink;
await preparePage(tester);
await pasteAsMention(tester, link);
Node node = tester.editor.getNodeAtPath([0]);
link = getMentionLink(node);
await hoverMentionAndClick(
tester,
MentionLinktErrorMenuCommand.removeLink.title,
);
node = tester.editor.getNodeAtPath([0]);
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{'insert': link},
]);
});
});
group('Paste as Bookmark', () {
Future<void> pasteAsBookmark(WidgetTester tester, String link) =>
pasteAs(tester, link, PasteMenuType.bookmark);
Future<void> hoverAndClick(
WidgetTester tester,
LinkPreviewMenuCommand command,
) async {
final bookmark = find.byType(CustomLinkPreviewBlockComponent);
expect(bookmark, findsOneWidget);
await tester.hoverOnWidget(
bookmark,
onHover: () async {
final menuButton = find.byFlowySvg(FlowySvgs.toolbar_more_m);
await tester.tapButton(menuButton);
final commandButton = find.text(command.title);
await tester.tapButton(commandButton);
},
);
}
testWidgets('paste a link as bookmark', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
final node = tester.editor.getNodeAtPath([0]);
checkBookmark(node, link);
});
testWidgets('paste a link as bookmark and convert to mention',
(tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.convertToMention);
final node = tester.editor.getNodeAtPath([0]);
checkMention(node, link);
});
testWidgets('paste a link as bookmark and convert to url', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.convertToUrl);
final node = tester.editor.getNodeAtPath([0]);
checkUrl(node, link);
});
testWidgets('paste a link as bookmark and convert to embed',
(tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.convertToEmbed);
final node = tester.editor.getNodeAtPath([0]);
checkEmbed(node, link);
});
testWidgets('paste a link as bookmark and copy link', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.copyLink);
final clipboardContent = await getIt<ClipboardService>().getData();
expect(clipboardContent.plainText, link);
});
testWidgets('paste a link as bookmark and replace link', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.replace);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyA,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.simulateKeyEvent(LogicalKeyboardKey.delete);
await tester.enterText(find.byType(TextFormField), unavailableLink);
await tester.tapButton(find.text(LocaleKeys.button_replace.tr()));
final node = tester.editor.getNodeAtPath([0]);
checkBookmark(node, unavailableLink);
});
testWidgets('paste a link as bookmark and remove link', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsBookmark(tester, link);
await hoverAndClick(tester, LinkPreviewMenuCommand.removeLink);
final node = tester.editor.getNodeAtPath([0]);
expect(node.type, ParagraphBlockKeys.type);
expect(node.delta!.toJson(), [
{'insert': link},
]);
});
});
group('Paste as Embed', () {
Future<void> pasteAsEmbed(WidgetTester tester, String link) =>
pasteAs(tester, link, PasteMenuType.embed);
Future<void> hoverAndConvert(
WidgetTester tester,
LinkEmbedConvertCommand command,
) async {
final embed = find.byType(LinkEmbedBlockComponent);
expect(embed, findsOneWidget);
await tester.hoverOnWidget(
embed,
onHover: () async {
final menuButton = find.byFlowySvg(FlowySvgs.turninto_m);
await tester.tapButton(menuButton);
final commandButton = find.text(command.title);
await tester.tapButton(commandButton);
},
);
}
testWidgets('paste a link as embed', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsEmbed(tester, link);
final node = tester.editor.getNodeAtPath([0]);
checkEmbed(node, link);
});
testWidgets('paste a link as bookmark and convert to mention',
(tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsEmbed(tester, link);
await hoverAndConvert(tester, LinkEmbedConvertCommand.toMention);
final node = tester.editor.getNodeAtPath([0]);
checkMention(node, link);
});
testWidgets('paste a link as bookmark and convert to url', (tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsEmbed(tester, link);
await hoverAndConvert(tester, LinkEmbedConvertCommand.toURL);
final node = tester.editor.getNodeAtPath([0]);
checkUrl(node, link);
});
testWidgets('paste a link as bookmark and convert to bookmark',
(tester) async {
final link = avaliableLink;
await preparePage(tester);
await pasteAsEmbed(tester, link);
await hoverAndConvert(tester, LinkEmbedConvertCommand.toBookmark);
final node = tester.editor.getNodeAtPath([0]);
checkBookmark(node, link);
});
});
}
@@ -1,4 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -31,4 +37,104 @@ void main() {
expect(pageFinder, findsNWidgets(1));
});
});
testWidgets('count title towards word count', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent();
Finder title = tester.editor.findDocumentTitle('');
await tester.openMoreViewActions();
final viewMetaInfo = find.byType(ViewMetaInfo);
expect(viewMetaInfo, findsOneWidget);
ViewMetaInfo viewMetaInfoWidget =
viewMetaInfo.evaluate().first.widget as ViewMetaInfo;
Counters titleCounter = viewMetaInfoWidget.titleCounters!;
expect(titleCounter.charCount, 0);
expect(titleCounter.wordCount, 0);
/// input [str1] within title
const str1 = 'Hello',
str2 = '$str1 AppFlowy',
str3 = '$str2!',
str4 = 'Hello world';
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.tapButton(title);
await tester.enterText(title, str1);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.openMoreViewActions();
viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo;
titleCounter = viewMetaInfoWidget.titleCounters!;
expect(titleCounter.charCount, str1.length);
expect(titleCounter.wordCount, 1);
/// input [str2] within title
title = tester.editor.findDocumentTitle(str1);
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.tapButton(title);
await tester.enterText(title, str2);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.openMoreViewActions();
viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo;
titleCounter = viewMetaInfoWidget.titleCounters!;
expect(titleCounter.charCount, str2.length);
expect(titleCounter.wordCount, 2);
/// input [str3] within title
title = tester.editor.findDocumentTitle(str2);
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.tapButton(title);
await tester.enterText(title, str3);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.openMoreViewActions();
viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo;
titleCounter = viewMetaInfoWidget.titleCounters!;
expect(titleCounter.charCount, str3.length);
expect(titleCounter.wordCount, 2);
/// input [str4] within document
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.editor
.updateSelection(Selection.collapsed(Position(path: [0])));
await tester.pumpAndSettle();
await tester.editor
.getCurrentEditorState()
.insertTextAtCurrentSelection(str4);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.openMoreViewActions();
final texts =
find.descendant(of: viewMetaInfo, matching: find.byType(FlowyText));
expect(texts, findsNWidgets(3));
viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo;
titleCounter = viewMetaInfoWidget.titleCounters!;
final Counters documentCounters = viewMetaInfoWidget.documentCounters!;
final wordCounter = texts.evaluate().elementAt(0).widget as FlowyText,
charCounter = texts.evaluate().elementAt(1).widget as FlowyText;
final numberFormat = NumberFormat();
expect(
wordCounter.text,
LocaleKeys.moreAction_wordCount.tr(
args: [
numberFormat
.format(titleCounter.wordCount + documentCounters.wordCount)
.toString(),
],
),
);
expect(
charCounter.text,
LocaleKeys.moreAction_charCount.tr(
args: [
numberFormat
.format(
titleCounter.charCount + documentCounters.charCount,
)
.toString(),
],
),
);
});
}
@@ -1,3 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -7,9 +12,23 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// +, ... button beside the block component.
group('document with option action button', () {
testWidgets(
'click + to add a block after current selection, and click + and option key to add a block before current selection',
group('block option action:', () {
Future<void> turnIntoBlock(
WidgetTester tester,
Path path, {
required String menuText,
required String afterType,
}) async {
await tester.editor.openTurnIntoMenu(path);
await tester.tapButton(
find.findTextInFlowyText(menuText),
);
final node = tester.editor.getCurrentEditorState().getNodeAtPath(path);
expect(node?.type, afterType);
}
testWidgets('''click + to add a block after current selection,
and click + and option key to add a block before current selection''',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -40,5 +59,120 @@ void main() {
expect(editorState.getNodeAtPath([0])?.delta?.toPlainText(), isEmpty);
expect(editorState.getNodeAtPath([2])?.delta?.toPlainText(), isEmpty);
});
testWidgets('turn into - single line', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
const name = 'Test Document';
await tester.createNewPageWithNameUnderParent(name: name);
await tester.openPage(name);
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('turn into');
// click the block option button to convert it to another blocks
final values = {
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
LocaleKeys.editor_bulletedListShortForm.tr():
BulletedListBlockKeys.type,
LocaleKeys.editor_numberedListShortForm.tr():
NumberedListBlockKeys.type,
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type,
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
};
for (final value in values.entries) {
final menuText = value.key;
final afterType = value.value;
await turnIntoBlock(
tester,
[0],
menuText: menuText,
afterType: afterType,
);
}
});
testWidgets('turn into - multi lines', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
const name = 'Test Document';
await tester.createNewPageWithNameUnderParent(name: name);
await tester.openPage(name);
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('turn into 1');
await tester.ime.insertCharacter('\n');
await tester.ime.insertText('turn into 2');
// click the block option button to convert it to another blocks
final values = {
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
LocaleKeys.editor_bulletedListShortForm.tr():
BulletedListBlockKeys.type,
LocaleKeys.editor_numberedListShortForm.tr():
NumberedListBlockKeys.type,
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type,
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
};
for (final value in values.entries) {
final editorState = tester.editor.getCurrentEditorState();
editorState.selection = Selection(
start: Position(path: [0]),
end: Position(path: [1], offset: 2),
);
final menuText = value.key;
final afterType = value.value;
await turnIntoBlock(
tester,
[0],
menuText: menuText,
afterType: afterType,
);
}
});
testWidgets(
'selecting the parent should deselect all the child nodes as well',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
const name = 'Test Document';
await tester.createNewPageWithNameUnderParent(name: name);
await tester.openPage(name);
// create a nested list
// Item 1
// Nested Item 1
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('Item 1');
await tester.ime.insertCharacter('\n');
await tester.simulateKeyEvent(LogicalKeyboardKey.tab);
await tester.ime.insertText('Nested Item 1');
// select the 'Nested Item 1' and then tap the option button of the 'Item 1'
final editorState = tester.editor.getCurrentEditorState();
final selection = Selection.collapsed(
Position(path: [0, 0], offset: 1),
);
editorState.selection = selection;
await tester.pumpAndSettle();
expect(editorState.selection, selection);
await tester.editor.hoverAndClickOptionMenuButton([0]);
expect(editorState.selection, Selection.collapsed(Position(path: [0])));
},
);
});
}
@@ -1,5 +1,6 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -47,5 +48,41 @@ void main() {
expect(editorState.selection!.start.offset, 0);
});
testWidgets('select and delete text', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
/// create a new document
await tester.createNewPageWithNameUnderParent();
/// input text
final editor = tester.editor;
final editorState = editor.getCurrentEditorState();
const inputText = 'Test for text selection and deletion';
final texts = inputText.split(' ');
await editor.tapLineOfEditorAt(0);
await tester.ime.insertText(inputText);
/// selecte and delete
int index = 0;
while (texts.isNotEmpty) {
final text = texts.removeAt(0);
await tester.editor.updateSelection(
Selection(
start: Position(path: [0], offset: index),
end: Position(path: [0], offset: index + text.length),
),
);
await tester.simulateKeyEvent(LogicalKeyboardKey.delete);
index++;
}
/// excpete the text value is correct
final node = editorState.getNodeAtPath([0])!;
final nodeText = node.delta?.toPlainText() ?? '';
expect(nodeText, ' ' * (index - 1));
});
});
}
@@ -1,16 +1,19 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/emoji.dart';
import '../../shared/util.dart';
// Test cases for the Document SubPageBlock that needs to be covered:
@@ -37,7 +40,14 @@ import '../../shared/util.dart';
const _defaultPageName = "";
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('Document SubPageBlock tests', () {
testWidgets('Insert a new SubPageBlock from Slash menu items',
@@ -48,11 +58,6 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
expect(
find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()),
findsNWidgets(3),
@@ -67,16 +72,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.pumpAndSettle();
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -94,15 +90,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionAddButton([0], false);
@@ -150,15 +138,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionAddButton([0], false);
@@ -211,15 +191,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor
@@ -255,15 +227,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor
@@ -308,15 +272,7 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -326,6 +282,11 @@ void main() {
expect(find.text('Child page'), findsNothing);
expect(find.byType(SubPageBlockComponent), findsNothing);
// Since there is no selection active in editor before deleting Node,
// we need to give focus back to the editor
await tester.editor
.updateSelection(Selection.collapsed(Position(path: [0])));
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isLinux || Platform.isWindows,
@@ -349,15 +310,7 @@ void main() {
await tester.insertSubPageFromSlashMenu(true);
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
// Delete
@@ -400,20 +353,16 @@ void main() {
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
expect(find.byType(SubPageBlockComponent), findsOneWidget);
await tester.hoverOnPageName('Child page');
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
'Child page',
onHover: () async {
await tester.tapDeletePageButton();
},
);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.text('Child page'), findsNothing);
@@ -426,16 +375,7 @@ void main() {
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
await tester.insertSubPageFromSlashMenu();
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
await tester.hoverOnPageName(_defaultPageName);
await tester.renamePage('Child page');
await tester.pumpAndSettle();
await tester.renamePageWithSecondary(_defaultPageName, 'Child page');
expect(find.text('Child page'), findsNWidgets(2));
await tester.editor.hoverAndClickOptionMenuButton([0]);
@@ -455,11 +395,6 @@ void main() {
await tester.insertSubPageFromSlashMenu(true);
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
expect(find.byType(SubPageBlockComponent), findsOneWidget);
final beforeNode = tester.editor.getNodeAtPath([1]);
@@ -473,6 +408,86 @@ void main() {
expect(afterNode.type, beforeNode.type);
expect(find.byType(SubPageBlockComponent), findsOneWidget);
});
testWidgets('turn into page', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock');
final editorState = tester.editor.getCurrentEditorState();
// Insert nested list
final transaction = editorState.transaction;
transaction.insertNode(
[0],
bulletedListNode(
text: 'Parent',
children: [
bulletedListNode(text: 'Child 1'),
bulletedListNode(text: 'Child 2'),
],
),
);
await editorState.apply(transaction);
await tester.pumpAndSettle();
expect(find.byType(SubPageBlockComponent), findsNothing);
await tester.editor.hoverAndClickOptionMenuButton([0]);
await tester.tapButtonWithName(
LocaleKeys.document_plugins_optionAction_turnInto.tr(),
);
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.editor_page.tr()));
await tester.pumpAndSettle();
expect(find.byType(SubPageBlockComponent), findsOneWidget);
await tester.expandOrCollapsePage(
pageName: 'SubPageBlock',
layout: ViewLayoutPB.Document,
);
expect(find.text('Parent'), findsNWidgets(2));
});
testWidgets('Displaying icon of subpage', (tester) async {
const firstPage = 'FirstPage';
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: firstPage);
final icon = await tester.loadIcon();
/// create subpage
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_subPage_name.tr(),
offset: 100,
);
/// add icon
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
await tester.tapIcon(icon);
await tester.pumpAndSettle();
await tester.openPage(firstPage);
await tester.expandOrCollapsePage(
pageName: firstPage,
layout: ViewLayoutPB.Document,
);
/// check if there is a icon in document
final iconWidget = find.byWidgetPredicate((w) {
if (w is! RawEmojiIconWidget) return false;
final iconData = w.emoji.emoji;
return iconData == icon.emoji;
});
expect(iconWidget, findsOneWidget);
});
});
}
@@ -498,4 +513,16 @@ extension _SubPageTestHelper on WidgetTester {
await pumpUntilFound(find.byType(SubPageBlockComponent));
}
Future<void> renamePageWithSecondary(
String currentName,
String newName,
) async {
await hoverOnPageName(currentName, onHover: () async => pumpAndSettle());
await rightClickOnPageName(currentName);
await tapButtonWithName(ViewMoreActionType.rename.name);
await enterText(find.byType(TextFormField), newName);
await tapOKButton();
await pumpAndSettle();
}
}
@@ -1,27 +1,12 @@
import 'package:integration_test/integration_test.dart';
import 'document_alignment_test.dart' as document_alignment_test;
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
import 'document_create_and_delete_test.dart'
as document_create_and_delete_test;
import 'document_inline_page_reference_test.dart'
as document_inline_page_reference_test;
import 'document_more_actions_test.dart' as document_more_actions_test;
import 'document_option_action_test.dart' as document_option_action_test;
import 'document_shortcuts_test.dart' as document_shortcuts_test;
import 'document_text_direction_test.dart' as document_text_direction_test;
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
import 'document_with_database_test.dart' as document_with_database_test;
import 'document_with_file_test.dart' as document_with_file_test;
import 'document_with_image_block_test.dart' as document_with_image_block_test;
import 'document_with_inline_math_equation_test.dart'
as document_with_inline_math_equation_test;
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
import 'document_with_multi_image_block_test.dart'
as document_with_multi_image_block_test;
import 'document_with_outline_block_test.dart' as document_with_outline_block;
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
import 'edit_document_test.dart' as document_edit_test;
void main() {
@@ -34,19 +19,5 @@ void main() {
document_with_inline_page_test.main();
document_with_inline_math_equation_test.main();
document_with_cover_image_test.main();
document_with_outline_block.main();
document_with_toggle_list_test.main();
document_copy_and_paste_test.main();
document_codeblock_paste_test.main();
document_alignment_test.main();
document_text_direction_test.main();
document_option_action_test.main();
document_with_image_block_test.main();
document_with_multi_image_block_test.main();
document_inline_page_reference_test.main();
document_more_actions_test.main();
document_with_file_test.main();
document_shortcuts_test.main();
// Don't add new tests here. Add them to document_test_runner_2.dart
// Don't add new tests here.
}
@@ -1,14 +1,26 @@
import 'package:integration_test/integration_test.dart';
import 'document_app_lifecycle_test.dart' as document_app_lifecycle_test;
import 'document_deletion_test.dart' as document_deletion_test;
import 'document_inline_sub_page_test.dart' as document_inline_sub_page_test;
import 'document_option_action_test.dart' as document_option_action_test;
import 'document_title_test.dart' as document_title_test;
import 'document_with_date_reminder_test.dart'
as document_with_date_reminder_test;
import 'document_with_toggle_heading_block_test.dart'
as document_with_toggle_heading_block_test;
import 'document_sub_page_test.dart' as document_sub_page_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_title_test.main();
// Disable subPage test temporarily, enable it in version 0.7.2
// document_sub_page_test.main();
document_app_lifecycle_test.main();
document_with_date_reminder_test.main();
document_deletion_test.main();
document_option_action_test.main();
document_inline_sub_page_test.main();
document_with_toggle_heading_block_test.main();
document_sub_page_test.main();
}
@@ -0,0 +1,22 @@
import 'package:integration_test/integration_test.dart';
import 'document_alignment_test.dart' as document_alignment_test;
import 'document_codeblock_paste_test.dart' as document_codeblock_paste_test;
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
import 'document_text_direction_test.dart' as document_text_direction_test;
import 'document_with_outline_block_test.dart' as document_with_outline_block;
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_with_outline_block.main();
document_with_toggle_list_test.main();
document_copy_and_paste_test.main();
document_codeblock_paste_test.main();
document_alignment_test.main();
document_text_direction_test.main();
// Don't add new tests here.
}
@@ -0,0 +1,33 @@
import 'package:integration_test/integration_test.dart';
import 'document_block_option_test.dart' as document_block_option_test;
import 'document_find_menu_test.dart' as document_find_menu_test;
import 'document_inline_page_reference_test.dart'
as document_inline_page_reference_test;
import 'document_more_actions_test.dart' as document_more_actions_test;
import 'document_shortcuts_test.dart' as document_shortcuts_test;
import 'document_toolbar_test.dart' as document_toolbar_test;
import 'document_with_file_test.dart' as document_with_file_test;
import 'document_with_image_block_test.dart' as document_with_image_block_test;
import 'document_with_multi_image_block_test.dart'
as document_with_multi_image_block_test;
import 'document_with_simple_table_test.dart'
as document_with_simple_table_test;
import 'document_link_preview_test.dart' as document_link_preview_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_with_image_block_test.main();
document_with_multi_image_block_test.main();
document_inline_page_reference_test.main();
document_more_actions_test.main();
document_with_file_test.main();
document_shortcuts_test.main();
document_block_option_test.main();
document_find_menu_test.main();
document_toolbar_test.main();
document_with_simple_table_test.main();
document_link_preview_test.main();
}
@@ -347,5 +347,27 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('paste text in title, check if the text is updated',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent();
await Clipboard.setData(const ClipboardData(text: _testDocumentName));
final title = tester.editor.findDocumentTitle('');
await tester.tapButton(title);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isMetaPressed: UniversalPlatform.isMacOS,
isControlPressed: !UniversalPlatform.isMacOS,
);
await tester.pumpAndSettle();
final newTitle = tester.editor.findDocumentTitle(_testDocumentName);
expect(newTitle, findsOneWidget);
});
});
}
@@ -0,0 +1,370 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
Future<void> selectText(WidgetTester tester, String text) async {
await tester.editor.updateSelection(
Selection.single(
path: [0],
startOffset: 0,
endOffset: text.length,
),
);
}
Future<void> prepareForToolbar(WidgetTester tester, String text) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent();
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText(text);
await selectText(tester, text);
}
group('document toolbar:', () {
testWidgets('font family', (tester) async {
await prepareForToolbar(tester, 'font family');
// tap more options button
await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_more_m);
// tap the font family button
final fontFamilyButton = find.byKey(kFontFamilyToolbarItemKey);
await tester.tapButton(fontFamilyButton);
// expect to see the font family dropdown immediately
expect(find.byType(FontFamilyDropDown), findsOneWidget);
// click the font family 'Abel'
const abel = 'Abel';
await tester.tapButton(find.text(abel));
// check the text is updated to 'Abel'
final editorState = tester.editor.getCurrentEditorState();
expect(
editorState.getDeltaAttributeValueInSelection(
AppFlowyRichTextKeys.fontFamily,
),
abel,
);
});
testWidgets('heading 1~3', (tester) async {
const text = 'heading';
await prepareForToolbar(tester, text);
Future<void> testChangeHeading(
FlowySvgData svg,
String title,
int level,
) async {
/// tap suggestions item
final suggestionsButton = find.byKey(kSuggestionsItemKey);
await tester.tapButton(suggestionsButton);
/// tap item
await tester.ensureVisible(find.byFlowySvg(svg));
await tester.tapButton(find.byFlowySvg(svg));
/// check the type of node is [HeadingBlockKeys.type]
await selectText(tester, text);
final editorState = tester.editor.getCurrentEditorState();
final selection = editorState.selection!;
final node = editorState.getNodeAtPath(selection.start.path)!,
nodeLevel = node.attributes[HeadingBlockKeys.level]!;
expect(node.type, HeadingBlockKeys.type);
expect(nodeLevel, level);
/// show toolbar again
await selectText(tester, text);
/// the text of suggestions item should be changed
expect(
find.descendant(of: suggestionsButton, matching: find.text(title)),
findsOneWidget,
);
}
await testChangeHeading(
FlowySvgs.type_h1_m,
LocaleKeys.document_toolbar_h1.tr(),
1,
);
await testChangeHeading(
FlowySvgs.type_h2_m,
LocaleKeys.document_toolbar_h2.tr(),
2,
);
await testChangeHeading(
FlowySvgs.type_h3_m,
LocaleKeys.document_toolbar_h3.tr(),
3,
);
});
testWidgets('toggle 1~3', (tester) async {
const text = 'toggle';
await prepareForToolbar(tester, text);
Future<void> testChangeToggle(
FlowySvgData svg,
String title,
int? level,
) async {
/// tap suggestions item
final suggestionsButton = find.byKey(kSuggestionsItemKey);
await tester.tapButton(suggestionsButton);
/// tap item
await tester.ensureVisible(find.byFlowySvg(svg));
await tester.tapButton(find.byFlowySvg(svg));
/// check the type of node is [HeadingBlockKeys.type]
await selectText(tester, text);
final editorState = tester.editor.getCurrentEditorState();
final selection = editorState.selection!;
final node = editorState.getNodeAtPath(selection.start.path)!,
nodeLevel = node.attributes[ToggleListBlockKeys.level];
expect(node.type, ToggleListBlockKeys.type);
expect(nodeLevel, level);
/// show toolbar again
await selectText(tester, text);
/// the text of suggestions item should be changed
expect(
find.descendant(of: suggestionsButton, matching: find.text(title)),
findsOneWidget,
);
}
await testChangeToggle(
FlowySvgs.type_toggle_list_m,
LocaleKeys.editor_toggleListShortForm.tr(),
null,
);
await testChangeToggle(
FlowySvgs.type_toggle_h1_m,
LocaleKeys.editor_toggleHeading1ShortForm.tr(),
1,
);
await testChangeToggle(
FlowySvgs.type_toggle_h2_m,
LocaleKeys.editor_toggleHeading2ShortForm.tr(),
2,
);
await testChangeToggle(
FlowySvgs.type_toggle_h3_m,
LocaleKeys.editor_toggleHeading3ShortForm.tr(),
3,
);
});
testWidgets('toolbar will not rebuild after click item', (tester) async {
const text = 'Test rebuilding';
await prepareForToolbar(tester, text);
Finder toolbar = find.byType(DesktopFloatingToolbar);
Element toolbarElement = toolbar.evaluate().first;
final elementHashcode = toolbarElement.hashCode;
final boldButton = find.byFlowySvg(FlowySvgs.toolbar_bold_m),
underlineButton = find.byFlowySvg(FlowySvgs.toolbar_underline_m),
italicButton = find.byFlowySvg(FlowySvgs.toolbar_inline_italic_m);
/// tap format buttons
await tester.tapButton(boldButton);
await tester.tapButton(underlineButton);
await tester.tapButton(italicButton);
toolbar = find.byType(DesktopFloatingToolbar);
toolbarElement = toolbar.evaluate().first;
/// check if the toolbar is not rebuilt
expect(elementHashcode, toolbarElement.hashCode);
final editorState = tester.editor.getCurrentEditorState();
/// check text formats
expect(
editorState
.getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.bold),
true,
);
expect(
editorState
.getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.italic),
true,
);
expect(
editorState
.getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.underline),
true,
);
});
});
group('document toolbar: link', () {
String? getLinkFromNode(Node node) {
for (final insert in node.delta!) {
final link = insert.attributes?.href;
if (link != null) return link;
}
return null;
}
bool isPageLink(Node node) {
for (final insert in node.delta!) {
final isPage = insert.attributes?.isPage;
if (isPage == true) return true;
}
return false;
}
String getNodeText(Node node) {
for (final insert in node.delta!) {
if (insert is TextInsert) return insert.text;
}
return '';
}
testWidgets('insert link and remove link', (tester) async {
const text = 'insert link', link = 'https://test.appflowy.cloud';
await prepareForToolbar(tester, text);
final toolbar = find.byType(DesktopFloatingToolbar);
expect(toolbar, findsOneWidget);
/// tap link button to show CreateLinkMenu
final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
await tester.tapButton(linkButton);
final createLinkMenu = find.byType(LinkCreateMenu);
expect(createLinkMenu, findsOneWidget);
/// test esc to close
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
expect(toolbar, findsNothing);
/// show toolbar again
await tester.editor.tapLineOfEditorAt(0);
await selectText(tester, text);
await tester.tapButton(linkButton);
/// insert link
final textField = find.descendant(
of: createLinkMenu,
matching: find.byType(TextFormField),
);
await tester.enterText(textField, link);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
Node node = tester.editor.getNodeAtPath([0]);
expect(getLinkFromNode(node), link);
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
/// hover link
await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
final hoverMenu = find.byType(LinkHoverMenu);
expect(hoverMenu, findsOneWidget);
/// copy link
final copyButton = find.descendant(
of: hoverMenu,
matching: find.byFlowySvg(FlowySvgs.toolbar_link_m),
);
await tester.tapButton(copyButton);
final clipboardContent = await getIt<ClipboardService>().getData();
final plainText = clipboardContent.plainText;
expect(plainText, link);
/// remove link
await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_link_unlink_m));
node = tester.editor.getNodeAtPath([0]);
expect(getLinkFromNode(node), null);
});
testWidgets('insert link and edit link', (tester) async {
const text = 'edit link',
link = 'https://test.appflowy.cloud',
afterText = '$text after';
await prepareForToolbar(tester, text);
/// tap link button to show CreateLinkMenu
final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
await tester.tapButton(linkButton);
/// search for page and select it
final textField = find.descendant(
of: find.byType(LinkCreateMenu),
matching: find.byType(TextFormField),
);
await tester.enterText(textField, gettingStarted);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
Node node = tester.editor.getNodeAtPath([0]);
expect(isPageLink(node), true);
expect(getLinkFromNode(node) == link, false);
/// hover link
await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
/// click edit button to show LinkEditMenu
final editButton = find.byFlowySvg(FlowySvgs.toolbar_link_edit_m);
await tester.tapButton(editButton);
final linkEditMenu = find.byType(LinkEditMenu);
expect(linkEditMenu, findsOneWidget);
/// change the link text
final titleField = find.descendant(
of: linkEditMenu,
matching: find.byType(TextFormField),
);
await tester.enterText(titleField, afterText);
await tester.pumpAndSettle();
await tester.tapButton(
find.descendant(of: linkEditMenu, matching: find.text(gettingStarted)),
);
final linkField = find.ancestor(
of: find.text(LocaleKeys.document_toolbar_linkInputHint.tr()),
matching: find.byType(TextFormField),
);
await tester.enterText(linkField, link);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
/// apply the change
final applyButton =
find.text(LocaleKeys.settings_appearance_documentSettings_apply.tr());
await tester.tapButton(applyButton);
node = tester.editor.getNodeAtPath([0]);
expect(isPageLink(node), false);
expect(getLinkFromNode(node), link);
expect(getNodeText(node), afterText);
});
});
}
@@ -1,17 +1,34 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../../shared/emoji.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('cover image:', () {
testWidgets('document cover tests', (tester) async {
@@ -51,6 +68,59 @@ void main() {
tester.expectToSeeNoDocumentCover();
});
testWidgets('document cover local image tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
tester.expectToSeeNoDocumentCover();
// Hover over cover toolbar to show 'Add Cover' and 'Add Icon' buttons
await tester.editor.hoverOnCoverToolbar();
// Insert a document cover
await tester.editor.tapOnAddCover();
tester.expectToSeeDocumentCover(CoverType.asset);
// Hover over the cover to show the 'Change Cover' and delete buttons
await tester.editor.hoverOnCover();
tester.expectChangeCoverAndDeleteButton();
// Change cover to a local image image
final imagePath = await rootBundle.load('assets/test/images/sample.jpeg');
final tempDirectory = await getTemporaryDirectory();
final localImagePath = p.join(tempDirectory.path, 'sample.jpeg');
final imageFile = File(localImagePath)
..writeAsBytesSync(imagePath.buffer.asUint8List());
await tester.editor.hoverOnCover();
await tester.editor.tapOnChangeCover();
final uploadButton = find.findTextInFlowyText(
LocaleKeys.document_imageBlock_upload_label.tr(),
);
await tester.tapButton(uploadButton);
mockPickFilePaths(paths: [localImagePath]);
await tester.tapButtonWithName(
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
);
await tester.pumpAndSettle();
tester.expectToSeeDocumentCover(CoverType.file);
// Remove the cover
await tester.editor.hoverOnCover();
await tester.editor.tapOnRemoveCover();
tester.expectToSeeNoDocumentCover();
// Test if deleteImageFromLocalStorage(localImagePath) function is called once
await tester.pump(kDoubleTapTimeout);
expect(deleteImageTestCounter, 1);
// delete temp files
await imageFile.delete();
});
testWidgets('document icon tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -147,7 +217,7 @@ void main() {
tester.expectViewHasIcon(
gettingStarted,
ViewLayoutPB.Document,
punch,
EmojiIconData.emoji(punch),
);
});
});
@@ -1,12 +1,15 @@
import 'package:appflowy/plugins/database/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -174,9 +177,110 @@ void main() {
findsOneWidget,
);
});
testWidgets('insert a referenced grid with many rows (load more option)',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await insertLinkedDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
// https://github.com/AppFlowy-IO/AppFlowy/issues/3533
// test: the selection of editor should be clear when editing the grid
await tester.editor.updateSelection(
Selection.collapsed(
Position(path: [1]),
),
);
final gridTextCell = find.byType(EditableTextCell).first;
await tester.tapButton(gridTextCell);
expect(tester.editor.getCurrentEditorState().selection, isNull);
final editorScrollable = find
.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byWidgetPredicate(
(w) => w is Scrollable && w.axis == Axis.vertical,
),
)
.first;
// Add 100 Rows to the linked database
final addRowFinder = find.byType(GridAddRowButton);
for (var i = 0; i < 100; i++) {
await tester.scrollUntilVisible(
addRowFinder,
100,
scrollable: editorScrollable,
);
await tester.tapButton(addRowFinder);
await tester.pumpAndSettle();
}
// Since all rows visible are those we added, we should see all of them
expect(find.byType(GridRow), findsNWidgets(103));
// Navigate to getting started
await tester.openPage(gettingStarted);
// Navigate back to the document
await tester.openPage('insert_a_reference_${ViewLayoutPB.Grid.name}');
// We see only 25 Grid Rows
expect(find.byType(GridRow), findsNWidgets(25));
// We see Add row and load more button
expect(find.byType(GridAddRowButton), findsOneWidget);
expect(find.byType(GridRowLoadMoreButton), findsOneWidget);
// Load more rows, expect 50 visible
await _loadMoreRows(tester, editorScrollable, 50);
// Load more rows, expect 75 visible
await _loadMoreRows(tester, editorScrollable, 75);
// Load more rows, expect 100 visible
await _loadMoreRows(tester, editorScrollable, 100);
// Load more rows, expect 103 visible
await _loadMoreRows(tester, editorScrollable, 103);
// We no longer see load more option
expect(find.byType(GridRowLoadMoreButton), findsNothing);
});
});
}
Future<void> _loadMoreRows(
WidgetTester tester,
Finder scrollable, [
int? expectedRows,
]) async {
await tester.scrollUntilVisible(
find.byType(GridRowLoadMoreButton),
100,
scrollable: scrollable,
);
await tester.pumpAndSettle();
await tester.tap(find.byType(GridRowLoadMoreButton));
await tester.pumpAndSettle();
if (expectedRows != null) {
expect(find.byType(GridRow), findsNWidgets(expectedRows));
}
}
/// Insert a referenced database of [layout] into the document
Future<void> insertLinkedDatabase(
WidgetTester tester,
@@ -0,0 +1,466 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/desktop_date_picker.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:table_calendar/table_calendar.dart';
import '../../shared/util.dart';
void main() {
setUp(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
TestWidgetsFlutterBinding.ensureInitialized();
});
group('date or reminder block in document:', () {
testWidgets("insert date with time block", (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'Date with time test',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
);
final dateTimeSettings = DateTimeSettingsPB(
dateFormat: UserDateFormatPB.Friendly,
timeFormat: UserTimeFormatPB.TwentyFourHour,
);
final DateTime currentDateTime = DateTime.now();
final String formattedDate =
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
// get current date in editor
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
// tap on date field
await tester.tap(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
// tap the toggle of include time
await tester.tap(find.byType(Toggle));
await tester.pumpAndSettle();
// add time 11:12
final textField = find
.descendant(
of: find.byType(DesktopAppFlowyDatePicker),
matching: find.byType(TextField),
)
.last;
await tester.pumpUntilFound(textField);
await tester.enterText(textField, "11:12");
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
// we will get field with current date and 11:12 as time
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate 11:12'), findsOneWidget);
});
testWidgets("insert date with reminder block", (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'Date with reminder test',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
);
final dateTimeSettings = DateTimeSettingsPB(
dateFormat: UserDateFormatPB.Friendly,
timeFormat: UserTimeFormatPB.TwentyFourHour,
);
final DateTime currentDateTime = DateTime.now();
final String formattedDate =
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
// get current date in editor
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
// tap on date field
await tester.tap(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
// tap reminder and set reminder to 1 day before
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
await tester.pumpAndSettle();
await tester.tap(
find.textContaining(
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
),
);
await tester.editor.tapLineOfEditorAt(0);
await tester.pumpAndSettle();
// we will get field with current date reminder_clock.svg icon
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
});
testWidgets("copy, cut and paste a date mention", (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'copy, cut and paste a date mention',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
);
final dateTimeSettings = DateTimeSettingsPB(
dateFormat: UserDateFormatPB.Friendly,
timeFormat: UserTimeFormatPB.TwentyFourHour,
);
final DateTime currentDateTime = DateTime.now();
final String formattedDate =
dateTimeSettings.dateFormat.formatDate(currentDateTime, false);
// get current date in editor
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
// update selection and copy
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: 1),
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyC,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
// update selection and paste
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsNWidgets(2));
expect(find.text('@$formattedDate'), findsNWidgets(2));
// update selection and cut
await tester.editor.updateSelection(
Selection(
start: Position(path: [0], offset: 1),
end: Position(path: [0], offset: 2),
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyX,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
// update selection and paste
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsNWidgets(2));
expect(find.text('@$formattedDate'), findsNWidgets(2));
});
testWidgets("copy, cut and paste a reminder mention", (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'copy, cut and paste a reminder mention',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
);
// trigger popup
await tester.tapButton(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
// set date to be fifteenth of the last month
await tester.tap(
find.descendant(
of: find.byType(DesktopAppFlowyDatePicker),
matching: find.byFlowySvg(FlowySvgs.arrow_left_s),
),
);
await tester.pumpAndSettle();
await tester.tap(
find.descendant(
of: find.byType(TableCalendar),
matching: find.text(15.toString()),
),
);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// add a reminder
await tester.tap(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
await tester.pumpAndSettle();
await tester.tap(
find.textContaining(
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
),
);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// verify
final dateTimeSettings = DateTimeSettingsPB(
dateFormat: UserDateFormatPB.Friendly,
timeFormat: UserTimeFormatPB.TwentyFourHour,
);
final now = DateTime.now();
final fifteenthOfLastMonth = DateTime(now.year, now.month - 1, 15);
final formattedDate =
dateTimeSettings.dateFormat.formatDate(fifteenthOfLastMonth, false);
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
// update selection and copy
await tester.editor.updateSelection(
Selection(
start: Position(path: [0]),
end: Position(path: [0], offset: 1),
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyC,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
// update selection and paste
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsNWidgets(2));
expect(find.text('@$formattedDate'), findsNWidgets(2));
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNWidgets(2));
expect(
getIt<ReminderBloc>().state.reminders.map((e) => e.id).toSet().length,
2,
);
// update selection and cut
await tester.editor.updateSelection(
Selection(
start: Position(path: [0], offset: 1),
end: Position(path: [0], offset: 2),
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyX,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
// update selection and paste
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyV,
isControlPressed: Platform.isLinux || Platform.isWindows,
isMetaPressed: Platform.isMacOS,
);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsNWidgets(2));
expect(find.text('@$formattedDate'), findsNWidgets(2));
expect(find.byType(MentionDateBlock), findsNWidgets(2));
expect(find.text('@$formattedDate'), findsNWidgets(2));
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNWidgets(2));
expect(
getIt<ReminderBloc>().state.reminders.map((e) => e.id).toSet().length,
2,
);
});
testWidgets("delete, undo and redo a reminder mention", (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'delete, undo and redo a reminder mention',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_dateOrReminder.tr(),
);
// trigger popup
await tester.tapButton(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
// set date to be fifteenth of the last month
await tester.tap(
find.descendant(
of: find.byType(DesktopAppFlowyDatePicker),
matching: find.byFlowySvg(FlowySvgs.arrow_left_s),
),
);
await tester.pumpAndSettle();
await tester.tap(
find.descendant(
of: find.byType(TableCalendar),
matching: find.text(15.toString()),
),
);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// add a reminder
await tester.tap(find.byType(MentionDateBlock));
await tester.pumpAndSettle();
await tester.tap(find.text(LocaleKeys.datePicker_reminderLabel.tr()));
await tester.pumpAndSettle();
await tester.tap(
find.textContaining(
LocaleKeys.datePicker_reminderOptions_oneDayBefore.tr(),
),
);
await tester.pumpAndSettle();
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
// verify
final dateTimeSettings = DateTimeSettingsPB(
dateFormat: UserDateFormatPB.Friendly,
timeFormat: UserTimeFormatPB.TwentyFourHour,
);
final now = DateTime.now();
final fifteenthOfLastMonth = DateTime(now.year, now.month - 1, 15);
final formattedDate =
dateTimeSettings.dateFormat.formatDate(fifteenthOfLastMonth, false);
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
// update selection and backspace to delete the mention
await tester.editor.updateSelection(
Selection.collapsed(Position(path: [0], offset: 1)),
);
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
await tester.pumpAndSettle();
expect(find.byType(MentionDateBlock), findsNothing);
expect(find.text('@$formattedDate'), findsNothing);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNothing);
expect(getIt<ReminderBloc>().state.reminders.isEmpty, isTrue);
// undo
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isWindows || Platform.isLinux,
isMetaPressed: Platform.isMacOS,
);
expect(find.byType(MentionDateBlock), findsOneWidget);
expect(find.text('@$formattedDate'), findsOneWidget);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsOneWidget);
expect(getIt<ReminderBloc>().state.reminders.map((e) => e.id).length, 1);
// redo
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isWindows || Platform.isLinux,
isMetaPressed: Platform.isMacOS,
isShiftPressed: true,
);
expect(find.byType(MentionDateBlock), findsNothing);
expect(find.text('@$formattedDate'), findsNothing);
expect(find.byFlowySvg(FlowySvgs.reminder_clock_s), findsNothing);
expect(getIt<ReminderBloc>().state.reminders.isEmpty, isTrue);
});
});
}
@@ -47,9 +47,7 @@ void main() {
mockPickFilePaths(paths: [filePath]);
await getIt<KeyValueStorage>().set(KVKeys.kCloudType, '0');
await tester.tap(
find.text(LocaleKeys.document_plugins_file_fileUploadHint.tr()),
);
await tester.tapFileUploadHint();
await tester.pumpAndSettle();
expect(find.byType(FileUploadMenu), findsNothing);
@@ -7,19 +7,16 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/cust
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart'
hide UploadImageMenu, ResizableImage;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import 'package:run_with_network_images/run_with_network_images.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
@@ -79,90 +76,6 @@ void main() {
file.deleteSync();
});
testWidgets('insert an image from network', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_image.tr(),
);
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
expect(find.byType(ImagePlaceholder), findsOneWidget);
expect(
find.descendant(
of: find.byType(ImagePlaceholder),
matching: find.byType(AppFlowyPopover),
),
findsOneWidget,
);
expect(find.byType(UploadImageMenu), findsOneWidget);
await tester.tapButtonWithName(
LocaleKeys.document_imageBlock_embedLink_label.tr(),
);
const url =
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640';
await tester.enterText(
find.descendant(
of: find.byType(EmbedImageUrlWidget),
matching: find.byType(TextField),
),
url,
);
await tester.tapButton(
find.descendant(
of: find.byType(EmbedImageUrlWidget),
matching: find.text(
LocaleKeys.document_imageBlock_embedLink_label.tr(),
findRichText: true,
),
),
);
await tester.pumpAndSettle();
expect(find.byType(ResizableImage), findsOneWidget);
final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
expect(node.type, ImageBlockKeys.type);
expect(node.attributes[ImageBlockKeys.url], url);
});
testWidgets('insert an image from unsplash', (tester) async {
await runWithNetworkImages(() async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(),
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_image.tr(),
);
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
expect(find.byType(ImagePlaceholder), findsOneWidget);
expect(
find.descendant(
of: find.byType(ImagePlaceholder),
matching: find.byType(AppFlowyPopover),
),
findsOneWidget,
);
expect(find.byType(UploadImageMenu), findsOneWidget);
expect(find.text('Unsplash'), findsOneWidget);
});
});
testWidgets('insert two images from local file at once', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@@ -1,9 +1,11 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -32,9 +34,15 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
// tap the more options button
final moreOptionButton = find.findFlowyTooltip(
LocaleKeys.document_toolbar_moreOptions.tr(),
);
await tester.tapButton(moreOptionButton);
// tap the inline math equation button
final inlineMathEquationButton = find.findFlowyTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
final inlineMathEquationButton = find.text(
LocaleKeys.document_toolbar_equation.tr(),
);
await tester.tapButton(inlineMathEquationButton);
@@ -77,10 +85,15 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
// tap the inline math equation button
var inlineMathEquationButton = find.findFlowyTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
// tap the more options button
final moreOptionButton = find.findFlowyTooltip(
LocaleKeys.document_toolbar_moreOptions.tr(),
);
await tester.tapButton(moreOptionButton);
// tap the inline math equation button
final inlineMathEquationButton =
find.byFlowySvg(FlowySvgs.type_formula_m);
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
@@ -92,17 +105,7 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: 1),
);
// expect to the see the inline math equation button is highlighted
inlineMathEquationButton = find.descendant(
of: find.findFlowyTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
),
matching: find.byType(SVGIconItemWidget),
);
expect(
tester.widget<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
isTrue,
);
await tester.tapButton(moreOptionButton);
// cancel the format
await tester.tapButton(inlineMathEquationButton);
@@ -113,5 +116,110 @@ void main() {
tester.expectToSeeText(formula);
});
testWidgets('insert a inline math equation and type something after it',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'math equation',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
const formula = 'E = MC ^ 2';
await tester.ime.insertText(formula);
await tester.editor.updateSelection(
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
// tap the more options button
final moreOptionButton = find.findFlowyTooltip(
LocaleKeys.document_toolbar_moreOptions.tr(),
);
await tester.tapButton(moreOptionButton);
// tap the inline math equation button
final inlineMathEquationButton =
find.byFlowySvg(FlowySvgs.type_formula_m);
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
final inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsOneWidget);
await tester.editor.tapLineOfEditorAt(0);
const text = 'Hello World';
await tester.ime.insertText(text);
final inlineText = find.textContaining(text, findRichText: true);
expect(inlineText, findsOneWidget);
// the text should be in the same line with the math equation
final inlineMathEquationPosition = tester.getRect(inlineMathEquation);
final textPosition = tester.getRect(inlineText);
// allow 5px difference
expect(
(textPosition.top - inlineMathEquationPosition.top).abs(),
lessThan(5),
);
expect(
(textPosition.bottom - inlineMathEquationPosition.bottom).abs(),
lessThan(5),
);
});
testWidgets('insert inline math equation by shortcut', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
// create a new document
await tester.createNewPageWithNameUnderParent(
name: 'insert inline math equation by shortcut',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
const formula = 'E = MC ^ 2';
await tester.ime.insertText(formula);
await tester.editor.updateSelection(
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
// mock key event
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyE,
isShiftPressed: true,
isControlPressed: true,
);
// expect to see the math equation block
final inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsOneWidget);
await tester.editor.tapLineOfEditorAt(0);
const text = 'Hello World';
await tester.ime.insertText(text);
final inlineText = find.textContaining(text, findRichText: true);
expect(inlineText, findsOneWidget);
// the text should be in the same line with the math equation
final inlineMathEquationPosition = tester.getRect(inlineMathEquation);
final textPosition = tester.getRect(inlineText);
// allow 5px difference
expect(
(textPosition.top - inlineMathEquationPosition.top).abs(),
lessThan(5),
);
expect(
(textPosition.bottom - inlineMathEquationPosition.bottom).abs(),
lessThan(5),
);
});
});
}
@@ -1,5 +1,5 @@
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -92,7 +92,21 @@ void main() {
);
expect(finder, findsOneWidget);
await tester.tapButton(finder);
expect(find.byType(AppFlowyErrorPage), findsOneWidget);
expect(find.byType(GridPage), findsOneWidget);
});
testWidgets('insert a inline page and type something after the page',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await insertInlinePage(tester, ViewLayoutPB.Grid);
await tester.editor.tapLineOfEditorAt(0);
const text = 'Hello World';
await tester.ime.insertText(text);
expect(find.textContaining(text, findRichText: true), findsOneWidget);
});
});
}
@@ -6,7 +6,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart';
@@ -26,7 +25,6 @@ import 'package:path_provider/path_provider.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
import '../board/board_hide_groups_test.dart';
void main() {
setUp(() {
@@ -1,9 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -46,6 +44,9 @@ void main() {
* # Heading 1
* ## Heading 2
* ### Heading 3
* > # Heading 1
* > ## Heading 2
* > ### Heading 3
*/
await tester.editor.tapLineOfEditorAt(3);
@@ -56,7 +57,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading1),
),
findsOneWidget,
findsNWidgets(2),
);
// Heading 2 is prefixed with a bullet
@@ -65,7 +66,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading2),
),
findsOneWidget,
findsNWidgets(2),
);
// Heading 3 is prefixed with a dash
@@ -74,7 +75,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading3),
),
findsOneWidget,
findsNWidgets(2),
);
// update the Heading 1 to Heading 1Hello world
@@ -102,13 +103,16 @@ void main() {
* # Heading 1
* ## Heading 2
* ### Heading 3
* > # Heading 1
* > ## Heading 2
* > ### Heading 3
*/
await tester.editor.tapLineOfEditorAt(3);
await tester.editor.tapLineOfEditorAt(7);
await insertOutlineInDocument(tester);
// expect to find only the `heading1` widget under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [3], 1);
await hoverAndClickDepthOptionAction(tester, [6], 1);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
@@ -126,7 +130,7 @@ void main() {
//////
/// expect to find only the 'heading1' and 'heading2' under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [3], 2);
await hoverAndClickDepthOptionAction(tester, [6], 2);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
@@ -137,13 +141,13 @@ void main() {
//////
// expect to find all the headings under the [OutlineBlockWidget]
await hoverAndClickDepthOptionAction(tester, [3], 3);
await hoverAndClickDepthOptionAction(tester, [6], 3);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text(heading1),
),
findsOneWidget,
findsNWidgets(2),
);
expect(
@@ -151,7 +155,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading2),
),
findsOneWidget,
findsNWidgets(2),
);
expect(
@@ -159,7 +163,7 @@ void main() {
of: find.byType(OutlineBlockWidget),
matching: find.text(heading3),
),
findsOneWidget,
findsNWidgets(2),
);
//////
});
@@ -172,7 +176,7 @@ Future<void> insertOutlineInDocument(WidgetTester tester) async {
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_name_outline.tr(),
offset: 100,
offset: 180,
);
await tester.pumpAndSettle();
}
@@ -182,19 +186,25 @@ Future<void> hoverAndClickDepthOptionAction(
List<int> path,
int level,
) async {
await tester.editor.hoverAndClickOptionMenuButton([3]);
await tester.tap(find.byType(AppFlowyPopover).hitTestable().last);
await tester.pumpAndSettle();
// Find a total of 4 HoverButtons under the [BlockOptionButton],
// in addition to 3 HoverButtons under the [DepthOptionAction] - (child of BlockOptionButton)
await tester.tap(find.byType(HoverButton).hitTestable().at(3 + level));
await tester.editor.openDepthMenu(path);
final type = OptionDepthType.fromLevel(level);
await tester.tapButton(find.findTextInFlowyText(type.description));
await tester.pumpAndSettle();
}
Future<void> insertHeadingComponent(WidgetTester tester) async {
await tester.editor.tapLineOfEditorAt(0);
// # heading 1-3
await tester.ime.insertText('# $heading1\n');
await tester.ime.insertText('## $heading2\n');
await tester.ime.insertText('### $heading3\n');
// > # toggle heading 1-3
await tester.ime.insertText('> # $heading1\n');
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
await tester.ime.insertText('> ## $heading2\n');
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
await tester.ime.insertText('> ### $heading3\n');
await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
}
Loaded 100 of 4797 files, more files were not shown because too many files have changed in this diff. Show more