Compare commits

..
121 Commits
Author SHA1 Message Date
Lucas.Xu 45cbd70bf4 chore: bump version 0.2.8 (#3105) 2023-08-03 15:07:01 +08:00
Lucas.Xu fb9bc359b8 feat: disable moving page into the database (#3107) 2023-08-03 15:06:02 +08:00
Nathan.fooo 135d8a811e chore: fix historical user open issue (#3106) 2023-08-03 12:07:42 +08:00
Nathan.fooo 03518c951c ci: disable document test (#3104) 2023-08-03 10:33:25 +08:00
Nathan.fooo 03b8f2ccb2 feat: Get started doc migration (#3102)
* feat: migrate empty document

* chore: update collab rev

* chore: fmt
2023-08-03 09:14:52 +08:00
Lucas.Xu a40c639a96 fix: dispose the listener when the favorite bloc dispose (#3103) 2023-08-03 08:50:56 +08:00
Nathan.foooandMathias Mogensen ea0c4e96d2 feat: implement magic link login (#3086)
* feat: implement magic link login

* ci: create env file

* ci: generate flutter env files

* ci: disable inject env

* chore: update table name

* Update frontend/appflowy_flutter/lib/env/env.dart

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

* chore: fix compile

---------

Co-authored-by: Mathias Mogensen <42929161+Xazin@users.noreply.github.com>
2023-08-03 08:48:04 +08:00
Mihir a1143e24f3 feat: support favorites folder 2023-08-02 21:20:51 +08:00
Richard Shiue ff79635b2b fix: height for calendar view in document (#3073) 2023-08-02 18:07:44 +08:00
Lucas.Xu f0d2cf7da3 fix: insert reference page in nested page 2023-08-01 21:47:20 +08:00
Richard Shiue 5e2ed56f5b fix: move translation jsons to right folder (#3050) 2023-08-01 14:19:45 +08:00
Mayur Mahajan 70963a55f1 chore: add c/cpp/auto options in code langauges 2023-08-01 14:16:01 +08:00
Lucas.Xu 62be81395d fix: missing c++ library when building iOS (#3092) 2023-08-01 14:15:19 +08:00
Vedant Pandey 7c21f61d2e feat: add duplicate in context menu (#2921) 2023-08-01 11:19:48 +08:00
Lucas.Xu ad4c68a353 chore: update iOS flowy infra UI name (#3091) 2023-08-01 10:07:12 +08:00
Yijing Huang 355061d415 fix: fix theme color (#3089) 2023-07-31 23:01:24 +08:00
Yijing Huang 3039944c74 feat: welcome screen UI improvement (#3057) 2023-07-31 23:01:12 +08:00
Yijing Huang 338e342410 feat: lavender theme refactor 2023-07-31 20:22:56 +08:00
Aman Negi 8c544bc8c4 fix: improve readability in callout 2023-07-31 20:18:43 +08:00
Lucas.Xu 266209caeb feat: implement draggable folder (#3083) 2023-07-31 20:06:01 +08:00
Kilu.He eb77346e5a feat: support add cover and icon in tauri document (#3069)
* feat: support add cover and icon

* feat: emoji picker

* feat: emoji picker
2023-07-31 11:39:44 +08:00
dejvizelo f28c5d849c feat: clear date value (#2700)
* chore: Add _ClearDateFieldButton

* chore: Add new DateCellCalendarEvent to clear date

* chore: Add grid_field_clearDate translation token

* refactor: _ClearDateFieldButton -> _ClearDateButton

* fix: Fix dart(unused_element) in _ClearDateButton

* feat: Handle clearDate

* Revert "feat: Handle clearDate"

This reverts commit fd145ef8e9589f3b4aa1a16e5d72c15b6d0b53c4.

* feat: Handle clearDate

* feat: Add clear_flag field

* feat: check clear_flag to clear date

* chore: update exisitng tests

* fix: add mssing import

* refactor: remove check for timeFormatError

* fix: fix margin on _ClearDateButton

* refactor: _ClearDateButton -> ClearDateButton

* test: add clear date backend unit test

* test: add clear date integration test

* test: add clear date to edit time cell test

* chore: mark ClearDateButton visible for testing

* feat: close date editor popover on clear date

* style: make AppFlowyPopover taller

* fix: trim dateStr in _dateStrFromCellData

* fix: don't dismiss CellEditor after clearDate
2023-07-31 10:23:20 +08:00
Richard ShiueandLucas.Xu ea37f46f88 fix: date field created when reference a calendar (#2910)
* fix: date field created when reference a calendar

* fix: make everything one transaction

* chore: apply suggestions from Lucas

* chore: do the same for board

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-31 10:12:26 +08:00
Nathan.fooo e68d3f1c71 tests: add supabase tests (#3084) 2023-07-30 08:39:01 +08:00
Jannes Blobel 5741feb7cc Add Transaltion section README.md (#2751)
* Add Transaltion section README.md

AppFlowy has a lot of missing translations. 
I thought you could use your community for the missing translation.

* Fix typo and add backticks for readability in README.md

* Update README.md
2023-07-29 15:00:25 +08:00
Nathan.foooandFu Zi Xiang 2cd88594e8 feat: migrate user data to cloud (#3078)
* refactor: weak passed-in params in handler

* refactor: rename struct

* chore: update tables

* chore: update schema

* chore: add permission

* chore: update tables

* chore: support transaction mode

* chore: workspace database id

* chore: add user workspace

* feat: return list of workspaces

* chore: add user to workspace

* feat: separate database row table

* refactor: update schema

* chore: partition table

* chore: use transaction

* refactor: dir

* refactor: collab db ref

* fix: collab db lock

* chore: rename files

* chore: add tables descriptions

* chore: update readme

* docs: update documentation

* chore: rename crate

* chore: update ref

* chore: update tests

* chore: update tests

* refactor: crate deps

* chore: update crate ref

* chore: remove unused deps

* chore: remove unused deps

* chore: update collab crate refs

* chore: replace client with transaction in pooler

* refactor: return error type

* refactor: use anyhow error in deps

* feat: supabase postgrest user signin (wip)

* fix: Cargo.toml source git deps, changed Error to anyhow::Error

* fix: uuid serialization

* chore: fix conflict

* chore: extend the response

* feat: add implementation place holders

* feat: impl get_user_workspaces

* feat: impl get_user_profile

* test: create workspace

* fix: postgrest: field names and alias

* chore: implement folder restful api

* chore: implement collab storate with restful api

* feat: added placeholders for impl: update_user_profile, check_user

* feat: impl: update_user_profile

* feat: impl: check_user

* fix: use UidResponse, add more debug info for serde serialization error

* fix: get_user_profile: use Optional<UserProfileResponse>

* chore: imple init sync

* chore: support soft delete

* feat: postgresql: add migration test

* feat: postgresql migration test: added UID display and colored output

* feat: postgresql migration test: workspace role

* feat: postgresql migration test: create shared common utils

* feat: postgresql migration test: fixed shebang

* chore: add flush_collab_update pg function

* chore: implement datbaase and document restful api

* chore: migrate to use restful api

* chore: update table schema

* chore: fix tests

* chore: remove unused code

* chore: format code

* chore: remove unused env

* fix: tauri build

* fix: tauri build

---------

Co-authored-by: Fu Zi Xiang <speed2exe@live.com.sg>
2023-07-29 09:46:24 +08:00
Kilu.He a885170869 feat: support editor format text color and bg color (#3061) 2023-07-27 20:40:18 +08:00
Kilu.He 915ce02157 fix: implement the interface of move nested views (#3042)
* fix: implement the interface of move nested views

* fix: update rust ci ubuntu version

* fix: update rust ci version
2023-07-26 16:49:50 +08:00
Lucas.Xu 6e9a5a16a6 fix: windows relesae job (#3038) 2023-07-25 16:41:03 +08:00
Richard Shiue 13c72a31b7 fix: cursor for block options on Windows (#3052) 2023-07-25 16:40:25 +08:00
Richard Shiue 96410b2d32 fix: text overflow when editing a filter 2023-07-25 09:07:21 +07:00
Lucas.Xu d32c2082b6 feat: support building on iOS platform (#3033) 2023-07-21 15:53:57 +08:00
Richard Shiue c368d855c7 feat: enable sort by date + sort fix (#2953) 2023-07-21 15:51:20 +08:00
Mayur Mahajan b1378b4545 feat: customize command shortcuts (#2848) 2023-07-20 16:11:00 +08:00
Kilu.He 5ab64f8835 feat: support views drag and drop (#3004) 2023-07-19 17:59:32 +08:00
Lucas.Xu 0fb004aee0 chore: bump version 0.2.7 (#3022)
* chore: bump version 0.2.7

* chore: update language files using inlang machine translation
2023-07-18 19:59:35 +08:00
Alex Wallen 06bbc06fff fix: focus is clickable if text field is being edited (#3020) 2023-07-18 18:25:25 +08:00
unknown f6edd4b32b fix: pasting a link in a URL grid crashes the app 2023-07-18 17:04:19 +07:00
Lucas.Xu 706a5e784f fix: cannot click on links (#3017) 2023-07-18 15:59:41 +08:00
Mayur Mahajan 1f720efc8a chore: clean unnecessary todos (#3018) 2023-07-18 10:47:55 +08:00
Mathias Mogensen 778e462523 feat: enable reordering properties from settings 2023-07-18 10:47:42 +08:00
Lucas.Xu 2da37122e4 feat: support toggle list (#3016) 2023-07-18 10:45:20 +08:00
Serge Brainin a00dd5498e feat: the application remembers being resized (#2907) 2023-07-18 10:45:06 +08:00
Yatendra Kumar 7dcc7c221f fix: row banner overlay (#3009) 2023-07-17 17:39:44 +08:00
Muhammad Usman Shahid bcd116797d chore: added a tooltip to theme mode for toggling theme 2023-07-17 17:39:20 +08:00
Harmon 70915df5ad fix: add newlines after device info entries for debug info (#3008) 2023-07-17 10:06:43 +08:00
Mayur Mahajan 1b582c3870 chore: change github logo (#3003) 2023-07-15 09:46:08 +08:00
Lucas.Xu 26f078128a test: add an option test (#3006) 2023-07-15 09:45:44 +08:00
Kilu.He c65584d23c feat: support navigator and trash page
* refactor: navigator

* feat: support trash
2023-07-14 20:33:22 +08:00
Lucas.Xu 098c085d96 chore: upgrade editor to 1.2.1 (#2997) 2023-07-14 17:08:40 +08:00
Richard Shiue 39988f6785 fix: field hide/delete icon color in dark mode (#2998) 2023-07-14 17:07:57 +08:00
GitStartandgitstart e69e72e3bd fix: Filter UI popover pixels overflow with dynamic width and height (#2554)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2023-07-14 11:06:32 +02:00
Nathan.fooo f9e7b5ffa4 feat: reload UI (#2999)
* chore: reload folder

* chore: reload folder

* chore: init sync

* chore: update tables

* chore: update database

* chore: load row

* chore: update

* chore: reload row

* test: fit test

* chore: retry

* chore: support batch fetch

* chore: enable sync

* chore: sync switch

* chore: sync switch

* chore: migration user data

* chore: migrate data

* chore: migrate folder

* chore: save user email

* chore: refresh user profile

* chore: fix test

* chore: delete translation files

* test: clippy format
2023-07-14 13:37:13 +08:00
Mathias Mogensen 5085ea115f feat: alt+click to add block above (#2988) 2023-07-13 18:33:16 +08:00
Daiki Nakashita c12aa66b2a fix: wrong grid row count (#2973) 2023-07-13 11:01:22 +08:00
Mohammad ZolfaghariandLucas.Xu d4a23657d7 fix: send database notification on create row (#2926)
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-13 09:39:16 +08:00
Mathias Mogensen 5b1afeb85d feat: open apps in tabs (#2962)
* feat: open apps in tabs

Closes: #2942 Relates: #2312

* fix: resolve comments

* fix: unfocus editor to close toolbar on open/change tab

* test: abstract open in a new tab helper
2023-07-12 13:13:18 +02:00
Richard Shiue d0747afbf2 fix: use same base width for all screen sizes (#2961) 2023-07-12 17:30:37 +08:00
Ikko Eltociear Ashimine 88405ad316 chore: fix typo in Dockerfile (#2966) 2023-07-12 17:28:53 +08:00
Roopesh Saravanan 5f3f681591 feat: add installation script for linux users (#2976) 2023-07-12 17:28:17 +08:00
Kilu.He 0dae8cf2f9 feat: support i18n in typescript (#2948) 2023-07-12 11:54:50 +08:00
Lucas.Xu 3d72b6fa12 chore: move language files to the root directory to share them with both Flutter and Tauri. (#2970) 2023-07-11 20:27:40 +08:00
Lucas.Xu 2c669f1b85 chore: bump version 0.2.6 (#2963) 2023-07-11 19:49:48 +08:00
Lucas.Xu b9976b072e fix: unable to cancel the inline math equation format (#2974)
* fix: unable to cancel the inline math equation format

* fix: bold font style used in the document is not suitable

* chore: add flutter clean before executing the release task

* fix: integration test
2023-07-11 19:49:29 +08:00
Felix HäberleandNathan.fooo 409e3bf1e3 feat: update translations (#2823)
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
2023-07-10 21:37:22 +08:00
Lucas.Xu ff9b3c56c5 feat:IInline math equation (#2949) 2023-07-09 11:03:22 +08:00
Kilu.He 4c17298432 feat: support dark mode (#2945) 2023-07-07 21:58:15 +08:00
Kilu.He 0401fc4b22 fix: tauri check user failed (#2946) 2023-07-06 16:40:20 +08:00
Mathias Mogensen 4805d0c568 Merge pull request #2939 from MayurSMahajan/fr_toggle_theme_shortcut_2304
feat: toggle theme mode shortcut
2023-07-06 09:34:00 +02:00
Nathan.foooandLucas.Xu edc7933c66 feat: support pg storage (#2935)
* refactor: using tokio-postgres

* chore: update

* chore: update env

* chore: update

* chore: upgrade supabase and add logout button

* refactor: update

* chore: update

* refactor: using message queue to handle the pg connection

* refactor: move test

* refactor: update sql

* chore: create pg database when user login

* chore: update scheme

* chore: generic user service

* chore: update

* chore: create statistics

* chore: create snapshot

* chore: add test

* chore: add database cloud service

* chore: add document cloud service

* chore: update interface

* test: add document test

* refactor: document interface

* chore: fix test

* chore: update

* chore: update test

* test: add test

* test: add test

* test: add test

* chore: update collab rev

* fix: flutter analyzer

* chore: update

* chore: update

* chore: update

* fix: tests

* chore: update

* chore: update collab rev

* ci: rust fmt

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-05 20:57:09 +08:00
MayurSMahajan 89ff44b035 test: hotkeys integration test 2023-07-05 17:46:10 +05:30
MayurSMahajan 793884427c feat: toggle theme mode shortcut 2023-07-05 10:58:36 +05:30
Richard Shiue e0ad364fa3 test: improve stability of calendar integration test (#2930) 2023-07-05 11:05:05 +08:00
Richard Shiue 5d76a7864d chore: restore default vscode build task (#2931) 2023-07-05 11:04:18 +08:00
Alex WallenandYijing Huang 323cb3b60f [feat] Allow user to select any Google Font (#2895)
* chore: add label for font selection drop down

* chore: add method to set font family

* feat: add drop down to setting appearance view

* feat: add fontFamily to document appearance cubit

* feat: add bloc provider to root for document appearance style

* feat: syncFont family from setting appearance dialog

* feat: plumbing for font style in editor

* fix: add blocprovider before pushing overlay

* chore: add kv_keys

* fix: use fontFamily in document appearance cubit

* fix: remove bloc providers because bloc is supplied in ancestor

* fix: remove unecessary bloc provider

* chore: add constraints to popover

* chore: add translation for search box

* feat: add levenshtein for string sort

* feat: add search bar view

* refactor: levenshtein

* chore: add tests for levenshtein algorithm

* feat: add unit tests for appearance cubit

* fix: analyzer warnings

* feat: sort by ascending if query is empty

* chore: add test for the font family setting widget

* feat: make comparison case insensitive

* feat: lazy load with listview.builder

Co-authored-by: Yijing Huang <hyj891204@gmail.com>

* fix: fonts loaded on open application

* fix: checkmark doesn't show

* fix: try catch before getFont

* fix: clear text editing value on close

* fix: remove autofocus for search text field

* chore: add tests

* feat: use sliver protocol

Co-authored-by: Yijing Huang <hyj891204@gmail.com>

* fix: avoid using intrinsic height

Co-authored-by: Yijing Huang <hyj891204@gmail.com>

* fix: extra paren caused build failure

* feat: switch order of font family setting

---------

Co-authored-by: Yijing Huang <hyj891204@gmail.com>
2023-07-04 11:30:38 -10:00
Kilu.He 9fb8f221cf chore: add document test (#2932) 2023-07-04 17:17:25 +08:00
8dfbfe3c42 feat: Dynamically Load Themes in AppFlowy (#2670)
* feat: dynamic theme plugin (init)

* feat: provide fallback color if plugin becomes out of date (transparent)

* feat: use applicationDocumentsDirectory to store plugins

* chore: remove json files

* fix: add toJson to resolve analyzer errors

* fix: analyzer (unused imports)

* feat: add code generation scripts for freezed files (call recursively in packages)

* fix: revert changes to dry generation

* feat: call directly into script

* refactor: scripts try to be stateless :)

* fix: path to code generation in toml

* fix: generate script permissions

* fix: path not correct in generate.sh

* feat: modify execution permissions before executing scripts

* chore: switch order of build_runner and easy_localizations

* fix: fs is not valid duckscript cmd

* chore: clean build_runner before executing

* chore: upgrade freezed and build_runner attempt to resolve InvalidType error

* fix: use exec cmd.exe to chmod

* feat: add task to generate all files

* chore: remove redundant task (Code Gen)

* chore: remove json_annoation to dev_dependencies

* fix: dropped & between commands

* chore: rename file and class to FlowyDynamicPlugin

* fix: dependency hell

* fix: json annotation in colorscheme

* fix: analyzer warnings

* fix: duckscript runner for code generator

* fix: try without setting file permissions

* chore: move file picker to infra

* chore: restructure project directory

* feat: add BLoC components for consumers

* chore: update dependencies in pubspec.yaml file

* fix: file picker imports

* feat: add new translations for features

* feat: add new widgets to render upload

* fix: import

* feat: add text overflow

* feat: use animated switcher

* chore: export FileType

* fix: directory was not created, only files were copied

* chore: separate some logic

* feat: add saveFile to FilePickerService

* fix: analyzer error with unused imports

* feat: add translations for uploading

* feat: add builtins property to apptheme

* feat: add theme preview widget

* fix: upload widgets need to fill whole space and account for overflow

* refactor: do not watch file system for changes

* feat: add deletion confirmation dialog

* feat: add form factor resolution for dyanmic layout

* feat: trigger rebuild only when plugins are loaded

* feat: make all methods static

* chore: remove TODO comment, requires further design

* chore: move models to subfolder

* fix: references to plugin service instance

* fix: rebase errors

* fix: more rebasing errors

* feat: remove multiple themes from one plugin

* refactor: use pattern to resolve widget in settings_appearance_view

* refactor: remove commented code

* feat: add translations

* fix: import error

* refactor: separate concerns a bit more

* fix: bug in toJson serialization code

* feat: add package to use represent memory files

* fix: analyzer warnings

* chore: add translation

* chore: remove unused exceptions

* chore: use join

* chore: add documentation

* feat: add tests on theme

* fix: fix scripts for macOS

* feat: use appFlowyDocumentDirectory

* fix: remove unused import

* fix: imports

* feat: allow plugin service to be passed

* fix: theme tests

* feat: separate themes by built-in and plugin

* fix: rebase change name of appFlowyDocumentDirectory

* chore: add test to check that initial state falls back to initial theme

* chore: theme upload preview widget

* chore: rename to brightness setting

* refactor: appearance for settings appearance view

* feat: change show dialog api and use it

* fix: handle plugin compilation exception when incorrect format supplied

* fix: style of theme upload

* fix: always change state so that ui updates

* chore: style of loading widget

* fix: analyzer errors

* feat: add learn more button to documentation

---------

Co-authored-by: Yijing Huang <hyj891204@gmail.com>
Co-authored-by: nathan <nathan@appflowy.io>
2023-07-03 22:07:11 +08:00
Nathan.fooo 453be8352a chore: add ref (#2927) 2023-07-03 11:14:45 +08:00
Kilu.He 452d7eb6d0 feat: support image block (#2912) 2023-07-03 10:04:40 +08:00
f0d5f51703 fix: windows integration test (#2917)
* fix:windows integration test

* fix: load asset

* fix: windows test

* fix: test

* test: refactor the folder test

---------

Co-authored-by: vedon <vedon.fu@gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-02 23:37:30 +08:00
Lucas.Xu 2c513be305 chore: bump version 0.2.5 (#2924) 2023-07-02 15:15:57 +08:00
Lucas.Xu 11d05b303d feat: support inserting local image (#2913) 2023-07-02 11:46:45 +08:00
Richard Shiue c870dbeac4 test: add more cover integration tests (#2922) 2023-07-01 23:13:09 +08:00
Richard ShiueandLucas.Xu 3acd36e580 test: fix calendar integration test #2919 (#2920)
* test: fix calendar integration test #2919

* chore: update frontend/appflowy_flutter/integration_test/database_calendar_test.dart

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

* test: fix date cell test

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-07-01 20:29:49 +08:00
Aman Negi 95d4fb6865 feat: add outline block (#2750) 2023-06-29 20:28:30 +08:00
Muhammad Rizwan 30b52a29fd feat: support Inline page reference #2196 (#2898) 2023-06-29 10:04:24 +08:00
Richard Shiue a17d6b7eec test: fix integration test (#2906)
* test: fix calendar integration test

* test: add comment for workaround
2023-06-28 22:11:50 +08:00
Kilu.He 18ed553296 feat: support equation block (#2903) 2023-06-28 12:39:04 +08:00
Richard Shiue 7f74fd6149 refactor: cover node widget code (#2899)
* chore: change initial cover type's name to none

* chore: refactor cover node widget

* chore: use a constant instead of magic value

* fix: make the size of icon hover effect smaller

* chore: improve appearance of selected color

* test: add cover integration tests

* fix: inner ring of selected color in dark mode

* refactor: cover node to document header node

* test: simplify tests

* chore: rename files
2023-06-27 15:17:51 +08:00
LainHE a3e09f6021 chore: update language zh-CN (#2900) 2023-06-27 11:11:09 +08:00
Kilu.He 5e0a0f92ff feat: support text equation (#2876) 2023-06-27 10:44:06 +08:00
Alex Wallen 2a2b5fe246 [feat] Add actions to error widget. (#2865)
* chore: rebase error?

* fix: revert changes in Cargo.toml

* feat: add actions row
2023-06-27 09:55:10 +08:00
rmri f914e7aa33 chore: Update hu-HU.json (#2837)
Fixed typos and added missing items in Hungarian translation.
2023-06-27 09:54:46 +08:00
Alex Wallen 386d3f2ba7 feat github redirect (#2867)
* feat: add GitHub to question bubble

* feat: translate to feedback
2023-06-27 09:53:38 +08:00
Nathan.fooo 88faa0f02a feat: using cached view (#2877) 2023-06-23 23:19:34 +08:00
Lucas.Xu f3d6c66f5c chore: bump version 0.2.4 (#2890) 2023-06-23 15:36:44 +08:00
Lucas.Xu d665153a9f fix: 0.2.3 known issues (#2886)
* fix: option menu position

* fix: rename helps highlight the text for the user

* fix: export as markdown file name invalid

* chore: align the emoji in callout

* fix: leave more space in the editor

* fix: 0.2.3 known issues

* chore: add flutter pub get in flutter.toml
2023-06-23 10:54:35 +08:00
Askarbek Zadaulyandqinluhe eee32110f4 feat: nested views
* chore: remove folder code merge page and folder into navitem component

* chore: test fix

* fix: nav item expand fix

* fix: unfold page and active page

* fix: nav item click area fix

* chore: remove old components

* chore: remove old code

* chore: cell controller reorganize

* chore: nav item optimizations

* fix: add async queue to fix data problem

* chore: change semantics of new folder button

* chore: move row methods to database controller

---------

Co-authored-by: qinluhe <qinluhe.twodog@gmail.com>
2023-06-23 10:17:50 +08:00
Richard Shiue 9834eccc7b fix: remove icon button not working in popover (#2885) 2023-06-23 10:01:06 +08:00
Richard Shiue bef7fe87aa test: add more calendar integration tests (#2883)
* test: make sure that the row details page opens

* test: add more calendar tests
2023-06-22 23:40:49 +08:00
Lucas.Xu feceb430cf chore: optimize CI (#2875) 2023-06-22 21:22:46 +08:00
Nathan.foooandvedon a29c8ab27a fix: cell focus issue on windows (#2880)
* fix: cell focus issue on windows

* fix: try to fix

* fix: cell focus not working on Windows platform there are multiple text cells

* docs: add documentation

* chore: adjust row detail page ui

* test: add test

* test: fix test

---------

Co-authored-by: vedon <vedon.fu@gmail.com>
2023-06-22 20:16:31 +08:00
Nathan.foooandvedon f1bfcb6066 feat: only allow one windows instance (#2879)
Co-authored-by: vedon <vedon.fu@gmail.com>
2023-06-22 13:25:01 +08:00
Richard Shiue 99cfda0a5c test: more integration tests (#2871)
* test: date cell integration tests

* test: single/multi-select integration tests
2023-06-21 22:53:49 +08:00
Lucas.Xu 9bd629aaef fix: undo redo for the transforming block will raise an error (#2869)
* fix: undo redo for the transforming block will raise an error

* test: add golden for editing document

* test: add undo redo test
2023-06-21 19:53:29 +08:00
Nathan.fooo d302f6b3fb chore: adjust ui (#2872) 2023-06-21 19:47:30 +08:00
Nathan.foooandLucas.Xu 5595649f53 chore: add database sort integration test (#2866)
* chore: add sort test

* chore: add tests

* chore: update tests

* fix: switch folder test

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-06-21 19:10:16 +08:00
Nathan.fooo 7de17ab0b8 chore: bump version 0.2.3 (#2864)
* chore: bump version 0.2.3

* chore: update version in pubspec
2023-06-21 15:13:36 +08:00
Nathan.fooo 13ff38756f feat: put all user data into a folder when choosing a custom storage path (#2861) 2023-06-21 12:15:32 +08:00
Lucas.Xu 197e5b3a79 feat: upgrade editor version to latest 0.1.4 2023-06-21 11:36:50 +08:00
Alex Wallen 5cced92646 [feat] make error widget sexy (#2825)
* feat: add translations

* feat: style error page

* chore: update api usage

* feat: add GitHub redirect

* chore: rebase error?

* chore: remove todo comment

* chore: remove impossible code

* fix: update url launcher invocation

* chore: use new api

* fix: analyzer errors

* fix: revert changes in Cargo.toml
2023-06-21 10:03:39 +08:00
Nathan.fooo e50d708c21 feat: create database view on same database (#2829)
* feat: create database view on same database

* feat: switch tag between views

* fix: calendar tool bar

* fix: set layout setting

* chore: update collab rev

* fix: board layout issue

* test: add integration tests

* test: add calendar start from day test
2023-06-20 23:48:34 +08:00
Nathan.fooo 79fc7c4cfe fix: import markdown file (#2841) 2023-06-19 21:20:53 +08:00
Kilu.He 202cd90711 fix: build tauri app failed in macOS (#2828) 2023-06-17 22:02:03 +08:00
Kilu.He 177f7c4fa3 Support document multiple instance and change doc_id from String to &str (#2808)
* fix: support multiple document

* fix: change the doc_id params to ref

* fix: function to converge subscription state

* fix: mousedown behavior update

* fix: turn to textblock when the enter pressed in empty block

* fix: support cut

* fix: emoji caret

* fix: support slash arrow key

* fix: eslint padding-line-between-statements

* fix: add comment

* fix: block side menu bugs

* fix: support key event to select block menu option

* fix: support side menu arrow key

* fix: optimizate selectOptionByUpDown

* fix: format
2023-06-17 14:25:30 +08:00
Lucas.Xu d986e01d3e chore: use retry action v1.0.36 2023-06-16 20:28:31 +08:00
Daniil Golubev 8a8915c662 chore: update russian localization (#2787)
* feat(locale): add missing lines

* feat(locale): update russian translations
2023-06-16 15:33:48 +08:00
efc857d752 chore: some ui improvements (#2791)
* chore: some ui improvements

* fix: integration test

* feat: language selector on welcome page (#2796)

* feat: add language selector on welcome page

* feat: add hover effect and refactor layout

* test: add basic languge selector testing

* chore: increate place holder width

* fix: add catch error for setLocale and finish the testing

* chore: update comment

* feat: refactor the skip login in page and add tests

---------

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

* feat: row document (#2792)

* chore: create orphan view handler

* feat: save icon url and cover url in view

* feat: implement emoji picker UI

* chore: config ui

* chore: config ui again

* chore: replace RowPB with RowMetaPB to exposing more row information

* fix: compile error

* feat: show emoji in row

* chore: update

* test: insert emoji test

* test: add update emoji test

* test: add remove emoji test

* test: add create field tests

* test: add create row and delete row integration tests

* test: add create row from row menu

* test: document in row detail page

* test: delete, duplicate row in row detail page

* test: check the row count displayed in grid page

* test: rename existing field in grid page

* test: update field type of exisiting field in grid page

* test: delete field test

* test: add duplicate field test

* test: add hide field test

* test: add edit text cell test

* test: add insert text to text cell test

* test: add edit number cell test

* test: add edit multiple number cells

* test: add edit checkbox cell test

* feat: integrate editor into database row

* test: add edit create time and last edit time cell test

* test: add edit date cell by selecting a date test

* chore: remove unused code

* chore: update checklist bg color

* test: add update database layout test

---------

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

* test: fix test

* test: add create select option test

---------

Co-authored-by: Yijing Huang <hyj891204@gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
Co-authored-by: nathan <nathan@appflowy.io>
2023-06-16 15:32:28 +08:00
Nathan.fooo 14dee6b797 test: filter integration (#2821)
* test: add checklist filter test

* fix: widget reference to invalid databaseController

* fix: SelectOptionFilterList doesn't expand to fill the CustomScrollView

* test: add single select and multi-select filter test

* ci: set protoc version
2023-06-16 14:32:32 +08:00
Lucas.Xu 63eee8b415 chore: retry codecov 2023-06-16 13:32:24 +08:00
Nathan.fooo d96a1d8bd4 test: import database integration (#2803)
* feat: support importing database raw data

* feat: verify import database test

* test: fix test

* ci: update integration test ci config

* ci: codecov with os flag

* ci: update docker command

* ci: update docker command

* ci: update docker command

* ci: update docker command

* test: add filter test
2023-06-15 22:43:07 +08:00
1089 changed files with 51816 additions and 15203 deletions

No files matched your search

+4 -3
View File
@@ -21,12 +21,13 @@ on:
- unlocked
- ready_for_review
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-app:
if: github.event.pull_request.draft != true
concurrency:
group: docker_ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
steps:
- name: Checkout source code
+18 -5
View File
@@ -25,8 +25,13 @@ env:
FLUTTER_VERSION: "3.10.1"
RUST_TOOLCHAIN: "1.70"
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: false
matrix:
@@ -119,9 +124,17 @@ jobs:
run: |
cargo make dart_unit_test
- uses: codecov/codecov-action@v3
- name: Upload coverage to Codecov
uses: Wandalen/wretry.action@v1.0.36
with:
name: appflowy
flags: appflowy_flutter_unit_test
fail_ci_if_error: true
verbose: true
action: codecov/codecov-action@v3
with: |
name: appflowy
flags: appflowy_flutter_unit_test
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
+64 -51
View File
@@ -6,7 +6,7 @@ on:
- "main"
- "release/*"
paths:
- ".github/workflows/flutter_ci.yaml"
- ".github/workflows/integration_test.yml"
- "frontend/**"
- "!frontend/appflowy_tauri/**"
@@ -15,52 +15,70 @@ on:
- "main"
- "release/*"
paths:
- ".github/workflows/flutter_ci.yaml"
- ".github/workflows/integration_test.yml"
- "frontend/**"
- "!frontend/appflowy_tauri/**"
env:
CARGO_TERM_COLOR: always
FLUTTER_VERSION: "3.10.1"
RUST_TOOLCHAIN: "1.70"
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
tests:
build:
if: github.event.pull_request.draft != true
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
os: [ubuntu-latest, windows-latest]
include:
- os: ubuntu-latest
flutter_profile: development-linux-x86_64
target: x86_64-unknown-linux-gnu
- os: windows-latest
flutter_profile: development-windows-x86
target: x86_64-pc-windows-msvc
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
with:
toolchain: "stable-2022-04-07"
- name: Checkout source code
uses: actions/checkout@v2
- uses: subosito/flutter-action@v2
- name: Install Rust toolchain
id: rust_toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ env.RUST_TOOLCHAIN }}
target: ${{ matrix.target }}
override: true
profile: minimal
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.1"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Cache Cargo
uses: actions/cache@v2
- uses: Swatinem/rust-cache@v2
with:
path: |
~/.cargo
key: ${{ runner.os }}-cargo-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
prefix-key: ${{ matrix.os }}
workspaces: |
frontend/rust-lib
cache-all-crates: true
- name: Cache Rust
id: cache-rust-target
uses: actions/cache@v2
- uses: davidB/rust-cargo-make@v1
with:
path: |
frontend/rust-lib/target
shared-lib/target
key: ${{ runner.os }}-rust-rust-lib-share-lib-${{ steps.rust_toolchain.outputs.rustc_hash }}-${{ hashFiles('./frontend/rust-lib/Cargo.toml') }}
version: '0.36.6'
- name: Setup Environment
- name: Install prerequisites
working-directory: frontend
run: |
cargo install --force cargo-make
cargo install --force duckscript_cli
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
@@ -73,56 +91,51 @@ jobs:
elif [ "$RUNNER_OS" == "macOS" ]; then
echo 'do nothing'
fi
cargo make appflowy-flutter-deps-tools
shell: bash
- if: steps.cache-cargo.outputs.cache-hit != 'true'
name: Rust Deps
working-directory: frontend
run: |
cargo install cargo-make
cargo make appflowy-flutter-deps-tools
- name: Config Flutter
- name: Enable Flutter Desktop
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
flutter config --enable-linux-desktop
elif [ "$RUNNER_OS" == "macOS" ]; then
flutter config --enable-macos-desktop
elif [ "$RUNNER_OS" == "Windows" ]; then
git config --system core.longpaths true
flutter config --enable-windows-desktop
fi
shell: bash
- name: Build Test lib
- name: Build AppFlowy
working-directory: frontend
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
cargo make --profile development-linux-x86_64 appflowy-dev
elif [ "$RUNNER_OS" == "macOS" ]; then
cargo make --profile development-mac-x86_64 appflowy-dev
elif [ "$RUNNER_OS" == "Windows" ]; then
cargo make --profile development-windows-x86 appflowy-dev
fi
shell: bash
cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev
- name: Run AppFlowy tests
- name: Run Flutter integration tests
working-directory: frontend/appflowy_flutter
run: |
if [ "$RUNNER_OS" == "Linux" ]; then
export DISPLAY=:99
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
sudo apt-get install network-manager
flutter test integration_test/runner.dart -d Linux --coverage --verbose
flutter test integration_test/runner.dart -d Linux --coverage
elif [ "$RUNNER_OS" == "macOS" ]; then
flutter test integration_test/runner.dart -d macOS --coverage --verbose
flutter test integration_test/runner.dart -d macOS --coverage
elif [ "$RUNNER_OS" == "Windows" ]; then
flutter test integration_test/runner.dart -d Windows --coverage --verbose
flutter test integration_test/runner.dart -d Windows --coverage
fi
shell: bash
- uses: codecov/codecov-action@v3
- name: Upload coverage to Codecov
uses: Wandalen/wretry.action@v1.0.36
with:
name: appflowy
flags: appflowy_flutter_integrateion_test
fail_ci_if_error: true
verbose: true
action: codecov/codecov-action@v3
with: |
name: appflowy
flags: appflowy_flutter_integrateion_test
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
+8
View File
@@ -20,6 +20,14 @@ jobs:
- name: Checkout
uses: actions/checkout@v2
# - name: Create .env file
# working-directory: frontend/appflowy_flutter
# run: |
# touch .env
# echo SUPABASE_URL=${{ secrets.HOST_URL }} >> .env
# echo SUPABASE_ANON_KEY=${{ secrets.HOST_ANON_KEY }} >> .env
# echo SUPABASE_JWT_SECRET=${{ secrets.HOST_JWT_SECRET }} >> .env
- name: Build release notes
run: |
touch ${{ env.RELEASE_NOTES_PATH }}
+1 -27
View File
@@ -22,7 +22,6 @@ on:
env:
CARGO_TERM_COLOR: always
RUST_TOOLCHAIN: "1.70"
FLUTTER_VERSION: "3.10.1"
jobs:
test-on-ubuntu:
@@ -40,14 +39,6 @@ jobs:
components: rustfmt, clippy
profile: minimal
- name: Install flutter
id: flutter
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: ${{ env.FLUTTER_VERSION }}
cache: true
- name: Install prerequisites
working-directory: frontend
run: |
@@ -60,11 +51,6 @@ jobs:
workspaces: |
frontend/rust-lib
- name: Build FlowySDK
working-directory: frontend
run: |
cargo make --profile development-linux-x86_64 appflowy-core-dev
- name: Run rust-lib tests
working-directory: frontend/rust-lib
run: RUST_LOG=info RUST_BACKTRACE=1 cargo test --no-default-features --features="rev-sqlite"
@@ -75,16 +61,4 @@ jobs:
- name: clippy rust-lib
run: cargo clippy --features="rev-sqlite"
working-directory: frontend/rust-lib
- name: rustfmt shared-lib
run: cargo fmt --all -- --check
working-directory: shared-lib
- name: clippy shared-lib
run: cargo clippy -- -D warnings
working-directory: shared-lib
- name: Run shared-lib tests
working-directory: shared-lib
run: RUST_LOG=info cargo test --no-default-features
working-directory: frontend/rust-lib
+7 -1
View File
@@ -2,6 +2,7 @@ name: Tauri-CI
on:
pull_request:
paths:
- ".github/workflows/tauri_ci.yaml"
- "frontend/rust-lib/**"
- "frontend/appflowy_tauri/**"
@@ -10,8 +11,13 @@ env:
PNPM_VERSION: "8.5.0"
RUST_TOOLCHAIN: "1.70"
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:
@@ -38,7 +44,7 @@ jobs:
with:
prefix-key: 'ubuntu-latest-tauri'
workspaces: |
frontend/rust-lib
frontend/rust-lib
frontend/appflowy_tauri/src-tauri
- name: install dependencies (windows only)
+81 -1
View File
@@ -1,24 +1,94 @@
# Release Notes
## Version 0.2.2 - 06/15/2023
## Version 0.2.8 - 08/03/2023
### New Features
- Nestable personal folder that supports drag and drop
- Support for favorite folders.
- Support for sorting by date in Grid view.
- Add a duplicate button in the Board context menu.
### Bug fixes
- Improve readability in Callout
- Some UI issues
## Version 0.2.7 - 07/18/2023
### New Features
<img width="1147" src="https://github.com/AppFlowy-IO/AppFlowy/assets/11863087/ac464740-c685-4a85-ae99-1074c1c607e5">
- Open page in new tab
- Create toggle lists to keep things tidy in your pages
- Alt/Option + click to add a text block above
### Bug fixes
- Pasting into a Grid property crashed on Windows
- Double-click a link to open
## Version 0.2.6 - 07/11/2023
### New Features
- Dynamic load themes
- Inline math equation
## Version 0.2.5 - 07/02/2023
### New Features
- Insert local images
- Mention a page
- Outlines (Table of contents)
- Added support for aligning the image by image menu
### Bug fixes
- Some UI issues
## Version 0.2.4 - 06/23/2023
### Bug fixes:
- Unable to copy and paste a word
- Some UI issues
## Version 0.2.3 - 06/21/2023
### New Features
- Added support for creating multiple database views for existing database
## Version 0.2.2 - 06/15/2023
### New Features
- Added support for embedding a document in the database's row detail page
- Added support for inserting an emoji in the database's row detail page
### Other Updates
- Added language selector on the welcome page
- Added support for importing multiple markdown files all at once
## Version 0.2.1 - 06/11/2023
### New Features
- Added support for creating or referencing a calendar in the document
- Added `+` icon in grid's add field
### Other Updates
- Added vertical padding for progress bar
- Hide url cell accessory when the content is empty
### Bug fixes:
- Fixed unable to export markdown
- Fixed adding vertical padding for progress bar
- Fixed database view didn't update after the database layout changed.
@@ -26,6 +96,7 @@
## Version 0.2.0 - 06/08/2023
### New Features
- Improved checklists to support each cell having its own list
- Drag and drop calendar events
- Switch layouts (calendar, grid, kanban) of a database
@@ -39,44 +110,53 @@
- Added support for an 'Option' button to delete, duplicate, and customize block actions
### Other Updates
- Added support for importing v0.1.x documents and databases
- Added support for database import and export to CSV
- Optimized scroll behavior in documents.
- Redesigned the launch page
### Bug fixes
- Fixed bugs related to numbers
- Fixed issues with referenced databases in documents
- Fixed menu overflow issues in documents
### Data migration
The data format of this version is not compatible with previous versions. Therefore, to migrate your data to the new version, you need to use the export and import functions. Please follow the guide to learn how to export and import your data.
#### Export files in v0.1.6
https://github.com/AppFlowy-IO/AppFlowy/assets/11863087/0c89bf2b-cd97-4a7b-b627-59df8d2967d9
#### Import files in v0.2.0
https://github.com/AppFlowy-IO/AppFlowy/assets/11863087/7b392f35-4972-497a-8a7f-f38efced32e2
## Version 0.1.5 - 11/05/2023
### Bug Fixes
- Fix: calendar dates don't match with weekdays.
- Fix: sort numbers in Grid.
## Version 0.1.4 - 04/05/2023
### New features
- Use AppFlowy’s calendar views to plan and manage tasks and deadlines.
- Writing can be improved with the help of OpenAI.
## Version 0.1.3 - 24/04/2023
### New features
- Launch the official Dark Mode.
- Customize the font color and highlight color by setting a hex color value and an opacity level.
### Bug Fixes
- Fix: the slash menu can be triggered by all other keyboards than English.
- Fix: convert the single asterisk to italic text and the double asterisks to bold text.
+8
View File
@@ -68,6 +68,14 @@ Contributions make the open-source community a fantastic place to learn, inspire
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.
## 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/appflowy_flutter/assets/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
<a href="https://github.com/AppFlowy-IO/AppFlowy/graphs/contributors">
+22 -45
View File
@@ -43,47 +43,34 @@
},
"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: 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-desktop: Debug Rust",
"request": "attach",
"type": "lldb",
"pid": "${command:pickMyProcess}"
},
// {
// "name": "AF-desktop: profile mode",
// "request": "launch",
// "program": "./lib/main.dart",
// "type": "dart",
// "flutterMode": "profile",
// "cwd": "${workspaceRoot}/appflowy_flutter"
// },
{
// This task builds the Rust and Dart code of AppFlowy for android.
"name": "AF-android: Build All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: build_mobile_sdk",
"env": {
"RUST_LOG": "info"
},
"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-android: Clean + Rebuild All",
"request": "launch",
"program": "./lib/main.dart",
"type": "dart",
"preLaunchTask": "AF: Clean + Rebuild All (Android)",
"env": {
"RUST_LOG": "info"
},
"cwd": "${workspaceRoot}/appflowy_flutter"
},
{
// https://tauri.app/v1/guides/debugging/vs-code
"type": "lldb",
@@ -99,15 +86,5 @@
"preLaunchTask": "AF: Tauri UI Dev",
"cwd": "${workspaceRoot}/appflowy_tauri/"
},
// {
// "type": "lldb",
// "request": "launch",
// "name": "AF-tauri: Production Debug",
// "cargo": {
// "args": ["build", "--release", "--manifest-path=./appflowy_tauri/src-tauri/Cargo.toml"]
// },
// "preLaunchTask": "AF: Tauri UI Build",
// "cwd": "${workspaceRoot}/appflowy_tauri/"
// },
]
}
+62 -14
View File
@@ -28,13 +28,13 @@
}
},
{
"label": "AF: Clean + Rebuild All (Android)",
"label": "AF: Clean + Rebuild All (iOS)",
"type": "shell",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Dart Clean",
"AF: Flutter Clean",
"AF: Build Appflowy Core_for_android",
"AF: Build Appflowy Core For iOS",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
@@ -46,9 +46,36 @@
}
},
{
"label": "AF: Build Appflowy Core_for_android",
"label": "AF: Clean + Rebuild All (iOS Simulator)",
"type": "shell",
"command": "cargo make --profile development-android appflowy-core-dev-android",
"dependsOrder": "sequence",
"dependsOn": [
"AF: Dart Clean",
"AF: Flutter Clean",
"AF: Build Appflowy Core For iOS Simulator",
"AF: Flutter Pub Get",
"AF: Flutter Package Get",
"AF: Generate Language Files",
"AF: Generate Freezed Files"
],
"presentation": {
"reveal": "always",
"panel": "new"
}
},
{
"label": "AF: Build Appflowy Core For iOS",
"type": "shell",
"command": "cargo make --profile development-ios-arm64 appflowy-core-dev-ios",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"
}
},
{
"label": "AF: Build Appflowy Core For iOS Simulator",
"type": "shell",
"command": "cargo make --profile development-ios-arm64-sim appflowy-core-dev-ios",
"group": "build",
"options": {
"cwd": "${workspaceFolder}"
@@ -118,15 +145,11 @@
{
"label": "AF: Generate Freezed Files",
"type": "shell",
"command": "dart run build_runner build -d",
"command": "sh ./scripts/code_generation/freezed/generate_freezed.sh",
"options": {
"cwd": "${workspaceFolder}/appflowy_flutter"
}
},
{
"label": "AF: Generate Language Files",
"type": "shell",
"command": "sh ./scripts/generate_language_files.sh",
"cwd": "${workspaceFolder}"
},
"group": "build",
"windows": {
"options": {
"shell": {
@@ -134,7 +157,24 @@
"args": [
"/d",
"/c",
".\\scripts\\generate_language_files.cmd"
".\\scripts\\code_generation\\freezed\\generate_freezed.cmd"
]
}
}
},
},
{
"label": "AF: Generate Language Files",
"type": "shell",
"command": "sh ./scripts/code_generation/language_files/generate_language_files.sh",
"windows": {
"options": {
"shell": {
"executable": "cmd.exe",
"args": [
"/d",
"/c",
".\\scripts\\code_generation\\language_files\\generate_language_files.cmd"
]
}
}
@@ -221,5 +261,13 @@
"cwd": "${workspaceFolder}/appflowy_tauri"
}
},
{
"label": "AF: Generate Env",
"type": "shell",
"command": "dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs ",
"options": {
"cwd": "${workspaceFolder}/appflowy_flutter"
}
},
]
}
}
+18 -7
View File
@@ -2,6 +2,7 @@
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" },
@@ -23,7 +24,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.2.2"
CURRENT_APP_VERSION = "0.2.8"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -150,6 +151,22 @@ LINUX_ARCH = "arm64"
APP_ENVIRONMENT = "production"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite,openssl_vendored"
[env.development-ios-arm64-sim]
BUILD_FLAG = "debug"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Debug"
RUST_COMPILE_TARGET = "aarch64-apple-ios-sim"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[env.development-ios-arm64]
BUILD_FLAG = "debug"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Debug"
RUST_COMPILE_TARGET = "aarch64-apple-ios"
BUILD_ARCHS = "arm64"
CRATE_TYPE = "staticlib"
[tasks.echo_env]
script = ['''
echo "-------- Env Parameters --------"
@@ -165,12 +182,6 @@ script = ['''
''']
script_runner = "@shell"
[env.production-ios]
BUILD_FLAG = "release"
TARGET_OS = "ios"
FLUTTER_OUTPUT_DIR = "Release"
PRODUCT_EXT = "ipa"
[env.development-android]
BUILD_FLAG = "debug"
TARGET_OS = "android"
+5
View File
@@ -70,5 +70,10 @@ windows/flutter/dart_ffi/
**/.vscode/
*.env
*.env.*
coverage/
**/failures/*.png
assets/translations/
@@ -0,0 +1 @@
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" style="width: 16px; height: 16px; display: block; fill: inherit; flex-shrink: 0; backface-visibility: hidden;" width="30" height="30" ><path d="M6.04883 27.3232C7.24219 27.3232 7.86426 26.4854 8.20703 25.3936L13.9834 6.79492H27.5039C28.2275 6.79492 28.7227 6.35059 28.7227 5.65234C28.7227 4.97949 28.2275 4.53516 27.5039 4.53516H13.958C12.6631 4.53516 12.0791 5.01758 11.7363 6.13477L6.18848 24.3525H5.97266L3.58594 15.9355C3.38281 15.2373 3.00195 14.9072 2.40527 14.9072C1.73242 14.9072 1.2373 15.3896 1.2373 16.0117C1.2373 16.2656 1.30078 16.4941 1.35156 16.6846L4.04297 25.5332C4.36035 26.5615 4.93164 27.3232 6.04883 27.3232ZM16.3955 24.7334C16.8652 24.7334 17.1064 24.5684 17.4619 24.0732L20.4707 19.8203H20.5215L23.5049 24.0732C23.873 24.5684 24.1143 24.7334 24.5713 24.7334C25.2061 24.7334 25.6758 24.3018 25.6758 23.7051C25.6758 23.4258 25.6123 23.1973 25.4219 22.9561L21.9307 18.208L25.4473 13.4346C25.6377 13.168 25.7139 12.9395 25.7139 12.6855C25.7139 12.127 25.2568 11.6953 24.6475 11.6953C24.2031 11.6953 23.9365 11.8477 23.6064 12.3174L20.7246 16.5957H20.6611L17.6904 12.3047C17.373 11.8477 17.0938 11.6953 16.624 11.6953C16.0146 11.6953 15.5068 12.165 15.5068 12.7363C15.5068 13.0537 15.583 13.2568 15.8115 13.5488L19.1504 18.1445L15.6084 23.0322C15.418 23.2861 15.3672 23.4639 15.3672 23.7559C15.3672 24.3018 15.8115 24.7334 16.3955 24.7334Z" fill="#2F3030"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" fill="#FFD667" stroke="#FFD667" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 329 B

@@ -1,3 +0,0 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16 6L18.781 11.9243L25 12.8801L20.5 17.489L21.562 24L16 20.9243L10.438 24L11.5 17.489L7 12.8801L13.219 11.9243L16 6Z" stroke="#333333" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 314 B

@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 4.5L14.0858 8.94322L18.75 9.66009L15.375 13.1167L16.1715 18L12 15.6932L7.8285 18L8.625 13.1167L5.25 9.66009L9.91425 8.94322L12 4.5Z" stroke="#333333" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3L9.3905 5.96215L12.5 6.44006L10.25 8.74448L10.781 12L8 10.4621L5.219 12L5.75 8.74448L3.5 6.44006L6.6095 5.96215L8 3Z" fill="#FFD667" stroke="#FFD667" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 334 B

After

Width:  |  Height:  |  Size: 315 B

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 3L9.3905 5.96215L12.5 6.44006L10.25 8.74448L10.781 12L8 10.4621L5.219 12L5.75 8.74448L3.5 6.44006L6.6095 5.96215L8 3Z" fill="#none" stroke="#FFFFFF" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

@@ -0,0 +1,4 @@
<svg width="207" height="205" viewBox="0 0 207 205" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="103.5" cy="102.5" rx="103.5" ry="102.5" fill="white"/>
<path d="M102.752 16C53.1943 16 13.0083 55.0187 13.0083 103.152C13.0083 141.658 38.7225 174.327 74.3807 185.851C78.8658 186.658 80.5127 183.96 80.5127 181.658C80.5127 179.58 80.4286 172.715 80.3907 165.433C55.4239 170.704 50.1557 155.15 50.1557 155.15C46.0731 145.076 40.1907 142.397 40.1907 142.397C32.0486 136.988 40.8049 137.1 40.8049 137.1C49.8163 137.714 54.5615 146.08 54.5615 146.08C62.5662 159.404 75.5565 155.552 80.6775 153.325C81.4831 147.693 83.8087 143.847 86.3755 141.671C66.442 139.468 45.4876 131.994 45.4876 98.6005C45.4876 89.0853 48.9932 81.3105 54.734 75.2072C53.8022 73.012 50.7306 64.1484 55.6034 52.1433C55.6034 52.1433 63.1397 49.8011 80.2898 61.077C87.4482 59.1461 95.1254 58.1779 102.752 58.1445C110.379 58.1779 118.063 59.1454 125.234 61.077C142.363 49.8018 149.889 52.144 149.889 52.144C154.774 64.1478 151.701 73.0127 150.769 75.2072C156.523 81.3105 160.005 89.0853 160.005 98.6005C160.005 132.074 139.011 139.445 119.027 141.602C122.245 144.307 125.114 149.611 125.114 157.742C125.114 169.402 125.01 178.787 125.01 181.658C125.01 183.978 126.625 186.695 131.174 185.838C166.813 174.302 192.495 141.646 192.495 103.153C192.495 55.0187 152.314 16 102.752 16ZM46.6206 140.15C46.4229 140.583 45.7211 140.713 45.0824 140.415C44.431 140.132 44.0658 139.541 44.2761 139.107C44.4696 138.661 45.1728 138.536 45.8221 138.835C46.4741 139.12 46.8457 139.716 46.6206 140.15ZM51.0349 143.975C50.6072 144.36 49.7701 144.181 49.2022 143.573C48.6153 142.965 48.5052 142.152 48.9392 141.762C49.3809 141.376 50.1921 141.557 50.7804 142.164C51.3679 142.779 51.4815 143.586 51.0349 143.975ZM54.0637 148.869C53.5133 149.24 52.6145 148.892 52.0585 148.118C51.5088 147.343 51.5088 146.413 52.0704 146.041C52.6278 145.668 53.5133 146.003 54.0763 146.773C54.6246 147.561 54.6246 148.49 54.063 148.87L54.0637 148.869ZM59.1847 154.537C58.6932 155.064 57.645 154.923 56.878 154.204C56.0935 153.501 55.8754 152.504 56.369 151.977C56.8668 151.449 57.9206 151.597 58.6932 152.311C59.4714 153.012 59.7098 154.017 59.1847 154.537ZM65.8047 156.451C65.5873 157.134 64.5777 157.444 63.5618 157.154C62.5466 156.855 61.8826 156.056 62.0873 155.366C62.2984 154.678 63.3122 154.356 64.3358 154.666C65.3496 154.963 66.0157 155.756 65.8047 156.451ZM73.3375 157.263C73.3627 157.981 72.501 158.577 71.4339 158.591C70.3612 158.614 69.4925 158.032 69.4806 157.324C69.4806 156.599 70.3234 156.009 71.3968 155.991C72.4639 155.971 73.3375 156.548 73.3375 157.263ZM80.7378 156.987C80.8654 157.689 80.1243 158.409 79.0642 158.601C78.023 158.785 77.0583 158.352 76.9258 157.657C76.7968 156.938 77.5519 156.218 78.5917 156.032C79.6532 155.853 80.6025 156.274 80.7378 156.987Z" fill="#161614"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

@@ -0,0 +1,11 @@
"{""id"":""2_OVWb"",""name"":""Name"",""field_type"":0,""visibility"":true,""width"":150,""type_options"":{""0"":{""data"":""""}},""is_primary"":true}","{""id"":""xjmOSi"",""name"":""Type"",""field_type"":3,""visibility"":true,""width"":150,""type_options"":{""3"":{""content"":""{\""options\"":[{\""id\"":\""t1WZ\"",\""name\"":\""s6\"",\""color\"":\""Lime\""},{\""id\"":\""GzNa\"",\""name\"":\""s5\"",\""color\"":\""Yellow\""},{\""id\"":\""l_8w\"",\""name\"":\""s4\"",\""color\"":\""Orange\""},{\""id\"":\""TzVT\"",\""name\"":\""s3\"",\""color\"":\""LightPink\""},{\""id\"":\""b5WF\"",\""name\"":\""s2\"",\""color\"":\""Pink\""},{\""id\"":\""AcHA\"",\""name\"":\""s1\"",\""color\"":\""Purple\""}],\""disable_color\"":false}""}},""is_primary"":false}","{""id"":""Hpbiwr"",""name"":""Done"",""field_type"":5,""visibility"":true,""width"":150,""type_options"":{""5"":{""is_selected"":false}},""is_primary"":false}","{""id"":""F7WLnw"",""name"":""checklist"",""field_type"":7,""visibility"":true,""width"":120,""type_options"":{""0"":{""data"":""""},""7"":{}},""is_primary"":false}","{""id"":""KABhMe"",""name"":""number"",""field_type"":1,""visibility"":true,""width"":120,""type_options"":{""1"":{""format"":0,""symbol"":""RUB"",""scale"":0,""name"":""Number""},""0"":{""scale"":0,""data"":"""",""format"":0,""name"":""Number"",""symbol"":""RUB""}},""is_primary"":false}","{""id"":""lEn6Bv"",""name"":""date"",""field_type"":2,""visibility"":true,""width"":120,""type_options"":{""2"":{""field_type"":2,""time_format"":1,""timezone_id"":"""",""date_format"":3},""0"":{""field_type"":2,""date_format"":3,""time_format"":1,""data"":"""",""timezone_id"":""""}},""is_primary"":false}","{""id"":""B8Prnx"",""name"":""url"",""field_type"":6,""visibility"":true,""width"":120,""type_options"":{""6"":{""content"":"""",""url"":""""},""0"":{""content"":"""",""data"":"""",""url"":""""}},""is_primary"":false}","{""id"":""MwUow4"",""name"":""multi-select"",""field_type"":4,""visibility"":true,""width"":240,""type_options"":{""0"":{""content"":""{\""options\"":[],\""disable_color\"":false}"",""data"":""""},""4"":{""content"":""{\""options\"":[{\""id\"":\""__Us\"",\""name\"":\""m7\"",\""color\"":\""Green\""},{\""id\"":\""n9-g\"",\""name\"":\""m6\"",\""color\"":\""Lime\""},{\""id\"":\""KFYu\"",\""name\"":\""m5\"",\""color\"":\""Yellow\""},{\""id\"":\""KftP\"",\""name\"":\""m4\"",\""color\"":\""Orange\""},{\""id\"":\""5lWo\"",\""name\"":\""m3\"",\""color\"":\""LightPink\""},{\""id\"":\""Djrz\"",\""name\"":\""m2\"",\""color\"":\""Pink\""},{\""id\"":\""2uRu\"",\""name\"":\""m1\"",\""color\"":\""Purple\""}],\""disable_color\"":false}""}},""is_primary"":false}"
"{""field_type"":0,""created_at"":1686793246,""data"":""A"",""last_modified"":1686793246}","{""last_modified"":1686793275,""created_at"":1686793261,""data"":""AcHA"",""field_type"":3}","{""created_at"":1686793241,""field_type"":5,""last_modified"":1686793241,""data"":""Yes""}","{""data"":""{\""options\"":[{\""id\"":\""pi1A\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""6Pym\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""erEe\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""pi1A\"",\""6Pym\""]}"",""created_at"":1686793302,""field_type"":7,""last_modified"":1686793308}","{""created_at"":1686793333,""field_type"":1,""data"":""-1"",""last_modified"":1686793333}","{""last_modified"":1686793370,""field_type"":2,""data"":""1685583770"",""include_time"":false,""created_at"":1686793370}","{""created_at"":1686793395,""data"":""appflowy.io"",""field_type"":6,""last_modified"":1686793399,""url"":""https://appflowy.io""}","{""last_modified"":1686793446,""field_type"":4,""data"":""2uRu"",""created_at"":1686793428}"
"{""last_modified"":1686793247,""data"":""B"",""field_type"":0,""created_at"":1686793247}","{""created_at"":1686793278,""data"":""b5WF"",""field_type"":3,""last_modified"":1686793278}","{""created_at"":1686793292,""last_modified"":1686793292,""data"":""Yes"",""field_type"":5}","{""data"":""{\""options\"":[{\""id\"":\""YHDO\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""QjtW\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""K2nM\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""YHDO\""]}"",""field_type"":7,""last_modified"":1686793318,""created_at"":1686793311}","{""data"":""-2"",""last_modified"":1686793335,""created_at"":1686793335,""field_type"":1}","{""field_type"":2,""data"":""1685670174"",""include_time"":false,""created_at"":1686793374,""last_modified"":1686793374}","{""last_modified"":1686793403,""field_type"":6,""created_at"":1686793399,""url"":"""",""data"":""no url""}","{""data"":""2uRu,Djrz"",""field_type"":4,""last_modified"":1686793449,""created_at"":1686793449}"
"{""data"":""C"",""created_at"":1686793248,""last_modified"":1686793248,""field_type"":0}","{""created_at"":1686793280,""field_type"":3,""data"":""TzVT"",""last_modified"":1686793280}","{""data"":""Yes"",""last_modified"":1686793292,""field_type"":5,""created_at"":1686793292}","{""last_modified"":1686793329,""field_type"":7,""created_at"":1686793322,""data"":""{\""options\"":[{\""id\"":\""iWM1\"",\""name\"":\""t1\"",\""color\"":\""Purple\""},{\""id\"":\""WDvF\"",\""name\"":\""t2\"",\""color\"":\""Purple\""},{\""id\"":\""w3k7\"",\""name\"":\""t3\"",\""color\"":\""Purple\""}],\""selected_option_ids\"":[\""iWM1\"",\""WDvF\"",\""w3k7\""]}""}","{""field_type"":1,""last_modified"":1686793339,""data"":""0.1"",""created_at"":1686793339}","{""last_modified"":1686793377,""data"":""1685756577"",""created_at"":1686793377,""include_time"":false,""field_type"":2}","{""created_at"":1686793403,""field_type"":6,""data"":""appflowy.io"",""last_modified"":1686793408,""url"":""https://appflowy.io""}","{""data"":""2uRu,Djrz,5lWo"",""created_at"":1686793453,""last_modified"":1686793454,""field_type"":4}"
"{""data"":""D"",""last_modified"":1686793249,""created_at"":1686793249,""field_type"":0}","{""data"":""l_8w"",""created_at"":1686793284,""last_modified"":1686793284,""field_type"":3}","{""data"":""Yes"",""created_at"":1686793293,""last_modified"":1686793293,""field_type"":5}",,"{""field_type"":1,""last_modified"":1686793341,""created_at"":1686793341,""data"":""0.2""}","{""created_at"":1686793379,""last_modified"":1686793379,""field_type"":2,""data"":""1685842979"",""include_time"":false}","{""last_modified"":1686793419,""field_type"":6,""created_at"":1686793408,""data"":""https://github.com/AppFlowy-IO/"",""url"":""https://github.com/AppFlowy-IO/""}","{""data"":""2uRu,Djrz,5lWo"",""last_modified"":1686793459,""field_type"":4,""created_at"":1686793459}"
"{""field_type"":0,""last_modified"":1686793250,""created_at"":1686793250,""data"":""E""}","{""field_type"":3,""last_modified"":1686793290,""created_at"":1686793290,""data"":""GzNa""}","{""last_modified"":1686793294,""created_at"":1686793294,""data"":""Yes"",""field_type"":5}",,"{""created_at"":1686793346,""field_type"":1,""last_modified"":1686793346,""data"":""1""}","{""last_modified"":1686793383,""data"":""1685929383"",""field_type"":2,""include_time"":false,""created_at"":1686793383}","{""field_type"":6,""url"":"""",""data"":"""",""last_modified"":1686793421,""created_at"":1686793419}","{""field_type"":4,""last_modified"":1686793465,""data"":""2uRu,Djrz,5lWo,KFYu,KftP"",""created_at"":1686793463}"
"{""field_type"":0,""created_at"":1686793251,""data"":"""",""last_modified"":1686793289}",,,,"{""data"":""2"",""field_type"":1,""created_at"":1686793347,""last_modified"":1686793347}","{""include_time"":false,""data"":""1685929385"",""last_modified"":1686793385,""field_type"":2,""created_at"":1686793385}",,
"{""created_at"":1686793254,""field_type"":0,""last_modified"":1686793288,""data"":""""}",,,,"{""created_at"":1686793351,""last_modified"":1686793351,""data"":""10"",""field_type"":1}","{""include_time"":false,""data"":""1686879792"",""field_type"":2,""created_at"":1686793392,""last_modified"":1686793392}",,
,,,,"{""last_modified"":1686793354,""created_at"":1686793354,""field_type"":1,""data"":""11""}",,,
,,,,"{""field_type"":1,""last_modified"":1686793356,""data"":""12"",""created_at"":1686793356}",,,
,,,,,,,
@@ -1,153 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Jo",
"welcomeText": "Benvingut a @:appName",
"githubStarText": "Preferit a Github",
"subscribeNewsletterText": "Subscriu-me al butlletí",
"letsGoButtonText": "Endavant",
"title": "Títol",
"signUp": {
"buttonText": "Registra't",
"title": "Registra't a @:appName",
"getStartedText": "Comencem",
"emptyPasswordError": "La contrasenya no pot ser buida",
"repeatPasswordEmptyError": "La contrasenya repetida no pot ser buida",
"unmatchedPasswordError": "Les contrasenyes no concorden",
"alreadyHaveAnAccount": "Ja tens un compte?",
"emailHint": "Correu electrònic",
"passwordHint": "Contrasenya",
"repeatPasswordHint": "Repeteix la contrasenya"
},
"signIn": {
"loginTitle": "Inicia sessió a @:appName",
"loginButtonText": "Inicia sessió",
"buttonText": "Inicia sessió",
"forgotPassword": "Has oblidat la contrasenya?",
"emailHint": "Correu electrònic",
"passwordHint": "Contrasenya",
"dontHaveAnAccount": "No tens un compte?",
"repeatPasswordEmptyError": "La contrasenya repetida no pot ser buida",
"unmatchedPasswordError": "Les contrasenyes no concorden"
},
"workspace": {
"create": "Crear un espai de treball",
"hint": "espai de treball",
"notFoundError": "No s'ha trobat l'espai de treball"
},
"shareAction": {
"buttonText": "Compartir",
"workInProgress": "Pròximament",
"markdown": "Markdown",
"copyLink": "Copiar l'enllaç"
},
"disclosureAction": {
"rename": "Canviar el nom",
"delete": "Eliminar",
"duplicate": "Duplicar"
},
"blankPageTitle": "Pàgina en blanc",
"newPageText": "Nova pàgina",
"trash": {
"text": "Paperera",
"restoreAll": "Recuperar-ho tot",
"deleteAll": "Eliminar-ho tot",
"pageHeader": {
"fileName": "Nom del fitxer",
"lastModified": "Última modificació",
"created": "Creat"
}
},
"deletePagePrompt": {
"text": "Aquest pàgina es troba a la paperera",
"restore": "Recuperar-la",
"deletePermanent": "Elimina-la"
},
"dialogCreatePageNameHint": "Nom de la pàgina",
"questionBubble": {
"whatsNew": "Què hi ha de nou?",
"help": "Ajuda i Suport",
"debug": {
"name": "Informació de depuració",
"success": "S'ha copiat la informació de depuració!",
"fail": "No es pot copiar la informació de depuració"
}
},
"menuAppHeader": {
"addPageTooltip": "Afegeix ràpidament una pàgina dins",
"defaultNewPageName": "Sense títol",
"renameDialog": "Canviar el nom"
},
"toolbar": {
"undo": "Desfer",
"redo": "Refer",
"bold": "Negreta",
"italic": "Cursiva",
"underline": "Text subratllar",
"strike": "Ratllat",
"numList": "Llista numerada",
"bulletList": "Llista de punts",
"checkList": "Llista de comprovació",
"inlineCode": "Inserir codi",
"quote": "Bloc citat",
"header": "Capçalera",
"highlight": "Subratllar"
},
"tooltip": {
"lightMode": "Canviar a mode clar",
"darkMode": "Canviar a mode fosc"
},
"contactsPage": {
"title": "Contactes",
"whatsHappening": "Que passa aquesta setmana?",
"addContact": "Afegir un contacte",
"editContact": "Editar un contacte"
},
"button": {
"OK": "OK",
"Cancel": "Cancel·lar",
"signIn": "Iniciar sessió",
"signOut": "Tancar sessió",
"complete": "Completar",
"save": "Guardar"
},
"label": {
"welcome": "Benvingut!",
"firstName": "Nom",
"middleName": "Segon Nom",
"lastName": "Cognom",
"stepX": "Pas {X}"
},
"oAuth": {
"err": {
"failedTitle": "No s'ha pogut connectar al teu compte.",
"failedMsg": "Assegureu-vos que heu completat el procés d'inici de sessió al vostre navegador."
},
"google": {
"title": "Iniciar sessió amb Google",
"instruction1": "Per importar els vostres contactes de Google, haureu d'autoritzar aquesta aplicació mitjançant el vostre navegador web.",
"instruction2": "Copia aquest codi clicant la icona o seleccionant el text:",
"instruction3": "Navega al següent enllaç amb el teu navegador i insereix el codi anterior:",
"instruction4": "Pressiona el botó d'avall una vegada hagis completat el registre:"
}
},
"settings": {
"title": "Configuració",
"menu": {
"appearance": "Aparença",
"language": "Idioma",
"open": "Obrir la configuració"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Mode Clar",
"dark": "Mode Fosc",
"system": "Adapt to System"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,159 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Ich",
"welcomeText": "Willkommen bei @:appName",
"githubStarText": "GitHub Star vergeben",
"subscribeNewsletterText": "Abonniere den Newsletter",
"letsGoButtonText": "Los geht's",
"title": "Titel",
"signUp": {
"buttonText": "Registrieren",
"title": "Registriere dich bei @:appName",
"getStartedText": "Erste Schritte",
"emptyPasswordError": "Passwort darf nicht leer sein",
"repeatPasswordEmptyError": "Passwortwiederholung darf nicht leer sein",
"unmatchedPasswordError": "Passwörter stimmen nicht überein",
"alreadyHaveAnAccount": "Bereits registriert?",
"emailHint": "E-Mail",
"passwordHint": "Passwort",
"repeatPasswordHint": "Wiederhole Passwort"
},
"signIn": {
"loginTitle": "Bei @:appName einloggen",
"loginButtonText": "Anmelden",
"buttonText": "Anmelden",
"forgotPassword": "Passwort vergessen?",
"emailHint": "E-Mail",
"passwordHint": "Passwort",
"dontHaveAnAccount": "Du besitzt noch kein Konto?",
"repeatPasswordEmptyError": "Passwortwiederholung darf nicht leer sein",
"unmatchedPasswordError": "Passwörter stimmen nicht überein"
},
"workspace": {
"create": "Arbeitsbereich erstellen",
"hint": "Arbeitsbereich",
"notFoundError": "Arbeitsbereich nicht gefunden"
},
"shareAction": {
"buttonText": "Teilen",
"workInProgress": "Demnächst verfügbar",
"markdown": "Markdown",
"copyLink": "Link kopieren"
},
"disclosureAction": {
"rename": "Umbenennen",
"delete": "Löschen",
"duplicate": "Duplizieren"
},
"blankPageTitle": "Leere Seite",
"newPageText": "Neue Seite",
"trash": {
"text": "Papierkorb",
"restoreAll": "Alles wiederherstellen",
"deleteAll": "Alles löschen",
"pageHeader": {
"fileName": "Dateiname",
"lastModified": "Letzte Änderung",
"created": "Erstellt"
}
},
"deletePagePrompt": {
"text": "Diese Seite ist im Papierkorb",
"restore": "Seite wiederherstellen",
"deletePermanent": "Dauerhaft löschen"
},
"dialogCreatePageNameHint": "Seitenname",
"questionBubble": {
"whatsNew": "Was gibt es Neues?",
"help": "Hilfe & Support",
"debug": {
"name": "Debug-Informationen",
"success": "Debug-Informationen in die Zwischenablage kopiert!",
"fail": "Debug-Informationen können nicht in die Zwischenablage kopiert werden"
},
"shortcuts": "Abkürzungen"
},
"menuAppHeader": {
"addPageTooltip": "Schnell eine Seite innerhalb hinzufügen",
"defaultNewPageName": "Unbenannt",
"renameDialog": "Umbenennen"
},
"toolbar": {
"undo": "Rückgängig",
"redo": "Wiederherstellen",
"bold": "Fett",
"italic": "Kursiv",
"underline": "Unterstreichen",
"strike": "Durchstreichen",
"numList": "Nummerierte Liste",
"bulletList": "Aufzählung",
"checkList": "Checkliste",
"inlineCode": "Inline-Code",
"quote": "Zitat",
"header": "Überschrift",
"highlight": "Hervorhebung",
"color": "Farbe"
},
"tooltip": {
"lightMode": "In den hellen Modus wechseln",
"darkMode": "In den dunklen Modus wechseln",
"openAsPage": "Als Seite öffnen"
},
"contactsPage": {
"title": "Kontakte",
"whatsHappening": "Was geschieht diese Woche?",
"addContact": "Kontakt hinzufügen",
"editContact": "Kontakt bearbeiten"
},
"button": {
"OK": "OK",
"Cancel": "Abbrechen",
"signIn": "Anmelden",
"signOut": "Abmelden",
"complete": "Fertig",
"save": "Speichern"
},
"label": {
"welcome": "Willkommen!",
"firstName": "Vorname",
"middleName": "Zweiter Vorname",
"lastName": "Nachname",
"stepX": "Schritt {X}"
},
"oAuth": {
"err": {
"failedTitle": "Keine Verbindung zu Ihrem Konto möglich.",
"failedMsg": "Bitte vergewissern Sie sich, dass Sie den Anmeldevorgang in Ihrem Browser abgeschlossen haben."
},
"google": {
"title": "GOOGLE ANMELDUNG",
"instruction1": "Um Ihre Google-Kontakte zu importieren, müssen Sie diese Anwendung über Ihren Webbrowser autorisieren.",
"instruction2": "Kopieren Sie diesen Code in Ihre Zwischenablage, indem Sie auf das Symbol klicken oder den Text auswählen:",
"instruction3": "Rufen Sie den folgenden Link in Ihrem Webbrowser auf, und geben Sie den obigen Code ein:",
"instruction4": "Klicken Sie unten auf die Schaltfläche, wenn Sie die Anmeldung abgeschlossen haben:"
}
},
"settings": {
"title": "Einstellungen",
"menu": {
"appearance": "Aussehen",
"language": "Sprache",
"open": "Einstellungen öffnen"
},
"appearance": {
"lightLabel": "Heller Modus",
"darkLabel": "Dunkler Modus"
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
},
"moreAction": {
"small": "klein",
"medium": "mittel",
"large": "groß",
"fontSize": "Schriftgröße",
"import": "Importieren"
}
}
@@ -1,231 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Mi",
"welcomeText": "Bienvenido a @:appName",
"githubStarText": "Favorito en GitHub",
"subscribeNewsletterText": "Suscribir al boletín",
"letsGoButtonText": "Vamos",
"title": "Título",
"signUp": {
"buttonText": "Registrar",
"title": "Registrar en @:appName",
"getStartedText": "Empezar",
"emptyPasswordError": "La contraseña no puede estar en blanco",
"repeatPasswordEmptyError": "La contraseña no puede estar en blanco",
"unmatchedPasswordError": "Las contraseñas no coinciden",
"alreadyHaveAnAccount": "¿Posee credenciales?",
"emailHint": "Correo",
"passwordHint": "Contraseña",
"repeatPasswordHint": "Repetir contraseña"
},
"signIn": {
"loginTitle": "Ingresa a @:appName",
"loginButtonText": "Ingresar",
"buttonText": "Ingresar",
"forgotPassword": "¿Olvidó su contraseña?",
"emailHint": "Correo",
"passwordHint": "Contraseña",
"dontHaveAnAccount": "¿No posee credenciales?",
"repeatPasswordEmptyError": "La contraseña no puede estar en blanco",
"unmatchedPasswordError": "Las contraseñas no coinciden"
},
"workspace": {
"create": "Crear espacio de trabajo",
"hint": "Espacio de trabajo",
"notFoundError": "Espacio de trabajo no encontrado"
},
"shareAction": {
"buttonText": "Compartir",
"workInProgress": "Próximamente",
"markdown": "Marcador",
"copyLink": "Copiar enlace"
},
"disclosureAction": {
"rename": "Renombrar",
"delete": "Eliminar",
"duplicate": "Duplicar"
},
"blankPageTitle": "Página en blanco",
"newPageText": "Nueva página",
"trash": {
"text": "Papelera",
"restoreAll": "Recuperar todo",
"deleteAll": "Eliminar todo",
"pageHeader": {
"fileName": "Nombre de archivo",
"lastModified": "Última modificación",
"created": "Creado"
}
},
"deletePagePrompt": {
"text": "Esta página está en la Papelera",
"restore": "Recuperar página",
"deletePermanent": "Eliminar permanentemente"
},
"dialogCreatePageNameHint": "Nombre de página",
"questionBubble": {
"whatsNew": "¿Qué hay de nuevo?",
"help": "Ayuda y Soporte",
"debug": {
"name": "Información de depuración",
"success": "¡Información copiada!",
"fail": "No fue posible copiar la información"
}
},
"menuAppHeader": {
"addPageTooltip": "Inserta una página",
"defaultNewPageName": "Sin Título",
"renameDialog": "Renombrar"
},
"toolbar": {
"undo": "Deshacer",
"redo": "Rehacer",
"bold": "Negrita",
"italic": "Cursiva",
"underline": "Subrayado",
"strike": "Tachado",
"numList": "Lista numerada",
"bulletList": "Lista con viñetas",
"checkList": "Lista de verificación",
"inlineCode": "Código embebido",
"quote": "Cita",
"header": "Título",
"highlight": "Resaltado",
"color": "Color"
},
"tooltip": {
"lightMode": "Cambiar a modo Claro",
"darkMode": "Cambiar a modo Oscuro"
},
"notifications": {
"export": {
"markdown": "Nota exportada a Markdown",
"path": "Documentos/flowy"
}
},
"contactsPage": {
"title": "Contactos",
"whatsHappening": "¿Qué está pasando esta semana?",
"addContact": "Agregar Contacto",
"editContact": "Editar Contacto"
},
"button": {
"OK": "OK",
"Cancel": "Cancelar",
"signIn": "Ingresar",
"signOut": "Salir",
"complete": "Completar",
"save": "Guardar"
},
"label": {
"welcome": "¡Bienvenido!",
"firstName": "Primer nombre",
"middleName": "Segundo nombre",
"lastName": "Apellido",
"stepX": "Paso {X}"
},
"oAuth": {
"err": {
"failedTitle": "Imposible conectarse con sus credenciales.",
"failedMsg": "Por favor asegurese haber completado el proceso de ingreso en su navegador."
},
"google": {
"title": "Ingresar con Google",
"instruction1": "Para importar sus contactos de Google, debe autorizar esta aplicación usando su navegador web.",
"instruction2": "Copie este código al presionar el icono o al seleccionar el texto:",
"instruction3": "Navege al siguiente enlace en su navegador web, e ingrese el código anterior:",
"instruction4": "Presione el botón de abajo cuando haya completado su registro:"
}
},
"settings": {
"title": "Ajustes",
"menu": {
"appearance": "Apariencia",
"language": "Lenguaje",
"open": "Abrir ajustes"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Modo Claro",
"dark": "Modo Oscuro",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "Filtrar",
"sortBy": "Ordenar por",
"Properties": "Propiedades"
},
"field": {
"hide": "Ocultar",
"insertLeft": "Insertar a la Izquierda",
"insertRight": "Insertar a la Derecha",
"duplicate": "Duplicar",
"delete": "Eliminar",
"textFieldName": "Texto",
"checkboxFieldName": "Casilla de verificación",
"dateFieldName": "Fecha",
"numberFieldName": "Números",
"singleSelectFieldName": "Seleccionar",
"multiSelectFieldName": "Selección múltiple",
"urlFieldName": "URL",
"numberFormat": "Formato numérico",
"dateFormat": "Formato de fecha",
"includeTime": "Incluir tiempo",
"dateFormatFriendly": "Mes Día, Año",
"dateFormatISO": "Año-Mes-Día",
"dateFormatLocal": "Mes/Día/Año",
"dateFormatUS": "Año/Mes/Día",
"timeFormat": "Formato de tiempo",
"invalidTimeFormat": "Formato de tiempo inválido",
"timeFormatTwelveHour": "12 horas",
"timeFormatTwentyFourHour": "24 horas",
"addSelectOption": "Añadir una opción",
"optionTitle": "Opciones",
"addOption": "Añadir opción",
"editProperty": "Editar propiedad"
},
"row": {
"duplicate": "Duplicar",
"delete": "Eliminar",
"textPlaceholder": "Vacío",
"copyProperty": "Propiedad copiada al portapapeles"
},
"selectOption": {
"create": "Crear",
"purpleColor": "Morado",
"pinkColor": "Rosa",
"lightPinkColor": "Rosa Claro",
"orangeColor": "Naranja",
"yellowColor": "Amarillo",
"limeColor": "Lima",
"greenColor": "Verde",
"aquaColor": "Agua",
"blueColor": "Azul",
"deleteTag": "Borrar etiqueta",
"colorPanelTitle": "Colores",
"panelTitle": "Selecciona una opción o crea una",
"searchOption": "Buscar una opción"
},
"menuName": "Cuadrícula"
},
"document": {
"menuName": "Documento",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"sideBar": {
"openSidebar": "Abrir panel lateral",
"closeSidebar": "Cerrar panel lateral"
},
"board": {
"column": {
"create_new_card": "Nuevo"
}
}
}
@@ -1,333 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Ni",
"welcomeText": "Ongietorri @:appName -ra",
"githubStarText": "Izarra GitHub-en",
"subscribeNewsletterText": "Harpidetu buletinera",
"letsGoButtonText": "Hasi",
"title": "Izenburua",
"signUp": {
"buttonText": "Izena eman",
"title": "Izena eman @:appName -ra",
"getStartedText": "Hasi",
"emptyPasswordError": "Pasahitzak ezin du hutsik egon",
"repeatPasswordEmptyError": "Pasahitz errepikapenak ezin du hutsik egon",
"unmatchedPasswordError": "Pasahitz errepikapena ez da berdina",
"alreadyHaveAnAccount": "Kontu bat duzu jada?",
"emailHint": "Emaila",
"passwordHint": "Pasahitza",
"repeatPasswordHint": "Pasahitza errepikatu"
},
"signIn": {
"loginTitle": "Hasi saioa @:appName -n",
"loginButtonText": "Hasi saioa",
"buttonText": "Sartu",
"forgotPassword": "Pasahitza ahaztu duzu?",
"emailHint": "Emaila",
"passwordHint": "Pasahitza",
"dontHaveAnAccount": "Ez daukazu konturik?",
"repeatPasswordEmptyError": "Pasahitz errepikapenak ezin du hutsik egon",
"unmatchedPasswordError": "Pasahitz errepikapena ez da berdina"
},
"workspace": {
"create": "Lan-eremua",
"hint": "lan-eremua",
"notFoundError": "Lan-eremurik ez da aurkitu"
},
"shareAction": {
"buttonText": "Konpartitu",
"workInProgress": "Laister",
"markdown": "Markdown",
"copyLink": "Esteka kopiatu"
},
"moreAction": {
"small": "txikia",
"medium": "ertaina",
"large": "handia",
"fontSize": "Letra tamaina"
},
"disclosureAction": {
"rename": "Izena aldatu",
"delete": "Ezabatu",
"duplicate": "Duplikatu"
},
"blankPageTitle": "Orri zuria",
"newPageText": "Orri berria",
"trash": {
"text": "Zaborrontzia",
"restoreAll": "Guztia berreskuratu",
"deleteAll": "Guztia ezabatu",
"pageHeader": {
"fileName": "Fitxategi izena",
"lastModified": "Azken aldaketa",
"created": "Sortua"
}
},
"deletePagePrompt": {
"text": "Orri hau zaborrontzian dago",
"restore": "Orria berreskuratu",
"deletePermanent": "Betirako ezabatu"
},
"dialogCreatePageNameHint": "Orriaren izena",
"questionBubble": {
"whatsNew": "Ze berri?",
"help": "Laguntza",
"debug": {
"name": "Debug informazioa",
"success": "Debug informazioa kopiatu da!",
"fail": "Ezin izan da debug informazioa kopiatu"
}
},
"menuAppHeader": {
"addPageTooltip": "Gehitu orri bat",
"defaultNewPageName": "Izenbururik ez",
"renameDialog": "Izena aldatu"
},
"toolbar": {
"undo": "Desegin",
"redo": "Berregin",
"bold": "Lodia",
"italic": "Etzana",
"underline": "Azpimarratua",
"strike": "Markatua",
"numList": "Zembakidun zerrenda",
"bulletList": "Buletetako zerrenda",
"checkList": "Egiaztapen zerrenda",
"inlineCode": "Lerroko kodea",
"quote": "Aipamena",
"header": "Goiburua",
"highlight": "Nabarmendu",
"color": "Kolorea"
},
"tooltip": {
"lightMode": "Modu argira aldatu",
"darkMode": "Modu ilunera aldatu",
"openAsPage": "Orri gisa ireki",
"addNewRow": "Ilara berri bat gehitu",
"openMenu": "Egin klik menua irekitzeko"
},
"sideBar": {
"closeSidebar": "Alboko barra itxi",
"openSidebar": "Alboko barra ireki"
},
"notifications": {
"export": {
"markdown": "Oharra markdownera esportatuta",
"path": "Documents/flowy"
}
},
"contactsPage": {
"title": "Kontaktuak",
"whatsHappening": "Ze berri aste honetan?",
"addContact": "Kontaktua gehitu",
"editContact": "Kontaktua editatu"
},
"button": {
"OK": "OK",
"Cancel": "Ezteztatu",
"signIn": "Saioa hasi",
"signOut": "Saioa itxi",
"complete": "Burututa",
"save": "Gorde"
},
"label": {
"welcome": "Ongi etorri!",
"firstName": "Izena",
"middleName": "Bigarren izena",
"lastName": "Abizena",
"stepX": "{X}. pausoa"
},
"oAuth": {
"err": {
"failedTitle": "Ezin izan da kontura sartu.",
"failedMsg": "Mesedez, ziurtatu zure arakatzailean saioa hasteko prozesua amaitu duzula."
},
"google": {
"title": "GOOGLE SAIOA HASI",
"instruction1": "Zure Google Kontaktuak inportatzeko, zure web arakatzailea erabiliz aplikazio hau baimendu beharko duzu.",
"instruction2": "Kopiatu kode hau ikonoan klik eginez edo testua hautatuz:",
"instruction3": "Nabigatu zure web arakatzailean esteka honetara eta idatzi goiko kodea:",
"instruction4": "Sakatu beheko botoia erregistroa amaitzean:"
}
},
"settings": {
"title": "Ezarpenak",
"menu": {
"appearance": "Itxura",
"language": "Hizkuntza",
"user": "Erabiltzailea",
"files": "Fitxategiak",
"open": "Ezarpenak ireki"
},
"appearance": {
"themeMode": {
"label": "Itxura modua",
"light": "Modu argia",
"dark": "Modu iluna",
"system": "Zure sistemara moldatu"
},
"theme": "Itxura"
},
"files": {
"defaultLocation": "Non gordetzen diren zure datuak",
"doubleTapToCopy": "Sakatu birritan bidea kopiatzeko",
"restoreLocation": "Berrezarri AppFlowy-ren biden lehenetsira",
"customizeLocation": "Beste karpeta bat ireki",
"restartApp": "Mesedez, berrabiarazi aplikazioa aldaketak indarrean egon daitezen.",
"exportDatabase": "Datubasea exportatu",
"selectFiles": "Aukeratu exportatu nahi dituzun fitxategiak",
"createNewFolder": "Karpeta berri bat sortu",
"createNewFolderDesc": "Non nahi dituzu datuak gorde ...",
"open": "Oreki",
"openFolder": "Ireki karpeta bat",
"openFolderDesc": "Irakurri eta idatzi zure AppFlowy karpetan...",
"folderHintText": "karpetaren izena",
"location": "Karpeta berria sortzen",
"locationDesc": "Aukeratu izen bat AppFlowy datuen karpetarako",
"browser": "Bilatu",
"create": "Sortu",
"folderPath": "Zure karpeta gordetzeko bidea",
"locationCannotBeEmpty": "Bideak ezin du hutsa egon"
}
},
"grid": {
"settings": {
"filter": "Filtroa",
"sort": "Ordenatu",
"sortBy": "Ordenatu honekiko",
"Properties": "Propietateak",
"group": "Taldea",
"addFilter": "Gehitu iragazkia",
"deleteFilter": "Ezabatu iragazkia",
"filterBy": "Iragazi arabera...",
"typeAValue": "Idatzi balio bat..."
},
"textFilter": {
"contains": "Dauka",
"doesNotContain": "Ez dauka",
"endsWith": "Honez amaitzen da",
"startWith": "Honez hasten da",
"is": "da",
"isNot": "Ez da",
"isEmpty": "Hutsa dago",
"isNotEmpty": "Ez dago hutsik",
"choicechipPrefix": {
"isNot": "Ez da",
"startWith": "Honez hasten da",
"endWith": "Honez amaitzen da",
"isEmpty": "hutsik dago",
"isNotEmpty": "ez dago hutsik"
}
},
"checkboxFilter": {
"isChecked": "Egiaztatuta",
"isUnchecked": "Desmarkatua",
"choicechipPrefix": {
"da": "da"
}
},
"checklistFilter": {
"isComplete": "osatu da",
"isIncomplted": "osatu gabe dago"
},
"singleSelectOptionFilter": {
"is": "da",
"isNot": "Ez da",
"isEmpty": "Hutsa dago",
"isNotEmpty": "Ez dago hutsik"
},
"multiSelectOptionFilter": {
"contains": "Duen",
"doesNotContain": "Ez dauka",
"isEmpty": "Hutsa dago",
"isNotEmpty": "Ez dago hutsik"
},
"field": {
"hide": "Ezkutatu",
"insertLeft": "Txertatu ezkerrera",
"insertRight": "Txertatu eskuinera",
"duplicate": "Bikoiztu",
"delete": "Ezabatu",
"textFieldName": "Testua",
"checkboxFieldName": "Markatu laukia",
"dateFieldName": "Data",
"numberFieldName": "Zenbakiak",
"singleSelectFieldName": "Hautatu",
"multiSelectFieldName": "Multi-hautaketa",
"urlFieldName": "URL",
"checklistFieldName": "Kontrol zerrenda",
"numberFormat": "Zenbaki formatua",
"dateFormat": "Data formatua",
"includeTime": "Sartu ordua",
"dateFormatFriendly": "Hilabete Eguna, Urtea",
"dateFormatISO": "Urtea-Hilabetea-Eguna",
"dateFormatLocal": "Hilabetea/Eguna/Urtea",
"dateFormatUS": "Urtea/Hilabetea/Eguna",
"timeFormat": "Denboraren formatua",
"invalidTimeFormat": "Formatu baliogabea",
"timeFormatTwelveHour": "12 ordu",
"timeFormatTwentyFourHour": "24 ordu",
"addSelectOption": "Gehitu aukera bat",
"optionTitle": "Aukerak",
"addOption": "Gehitu aukera",
"editProperty": "Editatu propietatea",
"newProperty": "Zutabe berria",
"deleteFieldPromptMessage": "Ziur al zaude? Propietate hau ezabatu egingo da"
},
"sort": {
"ascending": "Gorarantz",
"descending": "Jaisten",
"deleteSort": "Ezabatu ordena",
"addSort": "Gehitu ordenatu"
},
"row": {
"duplicate": "Bikoiztu",
"delete": "Ezabatu",
"textPlaceholder": "Hutsik",
"copyProperty": "Propietatea arbelean kopiatu da",
"count": "Kontatu",
"newRow": "Errenkada berria"
},
"selectOption": {
"create": "Sortu",
"purpleColor": "Purple",
"pinkColor": "Rosa",
"lightPinkColor": "Arrosa argia",
"orangeColor": "Laranja",
"yellowColor": "Horia",
"limeColor": "Lima",
"greenColor": "Berdea",
"aquaColor": "Aqua",
"blueColor": "Urdina",
"deleteTag": "Ezabatu etiketa",
"colorPanelTitle": "Koloreak",
"panelTitle": "Hautatu aukera bat edo sortu bat",
"searchOption": "Aukera bat bilatu"
},
"checklist": {
"panelTitle": "Gehitu elementu bat"
},
"menuName": "Sareta"
},
"document": {
"menuName": "Dokumentua",
"data": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
"column": {
"create_new_card": "Berria"
}
},
"calendar": {
"menuName": "Egutegia",
"navigation": {
"today": "Gaur",
"jumpToday": "Gaurko egunera salto egin",
"previousMonth": "Aurreko hilabetea",
"nextMonth": "Hurrengo hilabetea"
}
}
}
@@ -1,153 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Moi",
"welcomeText": "Bienvenue sur @:appName",
"githubStarText": "Favoriser sur GitHub",
"subscribeNewsletterText": "Abonnez-vous à notre courriel",
"letsGoButtonText": "Allons-y",
"title": "Titre",
"signUp": {
"buttonText": "S'inscrire",
"title": "S'inscrire à @:appName",
"getStartedText": "Commencer",
"emptyPasswordError": "Vous n'avez pas saisi votre mot de passe",
"repeatPasswordEmptyError": "Mot de passe ne doit pas être vide",
"unmatchedPasswordError": "Les deux mots de passe ne sont pas identiques",
"alreadyHaveAnAccount": "Avez-vous déjà un compte?",
"emailHint": "courriel",
"passwordHint": "Mot de passe",
"repeatPasswordHint": "Ressaisir votre mot de passe"
},
"signIn": {
"loginTitle": "Connexion à @:appName",
"loginButtonText": "Connexion",
"buttonText": "Se connecter",
"forgotPassword": "Mot de passe oublié?",
"emailHint": "courriel",
"passwordHint": "Mot de passe",
"dontHaveAnAccount": "Vous n'avez pas de compte?",
"repeatPasswordEmptyError": "Mot de passe ne doit pas être vide",
"unmatchedPasswordError": "Les deux mots de passe ne sont pas identiques"
},
"workspace": {
"create": "Créer un espace de travail",
"hint": "Espace de travail",
"notFoundError": "Espace de travail introuvable"
},
"shareAction": {
"buttonText": "Partager",
"workInProgress": "Bientôt disponible",
"markdown": "Markdown",
"copyLink": "Copier le lien"
},
"disclosureAction": {
"rename": "Renommer",
"delete": "Supprimer",
"duplicate": "Dupliquer"
},
"blankPageTitle": "Page vierge",
"newPageText": "Nouvelle page",
"trash": {
"text": "Corbeille",
"restoreAll": "Tout récupérer",
"deleteAll": "Tout supprimer",
"pageHeader": {
"fileName": "Nom de fichier",
"lastModified": "Dernière modification",
"created": "Créé"
}
},
"deletePagePrompt": {
"text": "Cette page est dans la corbeille",
"restore": "Récupérer la page",
"deletePermanent": "Supprimer définitivement"
},
"dialogCreatePageNameHint": "Nom de la page",
"questionBubble": {
"whatsNew": "Nouveautés",
"help": "Aide et Support Technique",
"debug": {
"name": "Infos du système",
"success": "Info copié!",
"fail": "Impossible de copier l'info"
}
},
"menuAppHeader": {
"addPageTooltip": "Ajouter une page",
"defaultNewPageName": "Sans titre",
"renameDialog": "Renommer"
},
"toolbar": {
"undo": "Annuler",
"redo": "Rétablir",
"bold": "Gras",
"italic": "Italique",
"underline": "Souligner",
"strike": "Barré",
"numList": "Liste numérotée",
"bulletList": "Liste à puces",
"checkList": "Liste de contrôle",
"inlineCode": "Code en ligne",
"quote": "Citation",
"header": "En-tête",
"highlight": "Surligner"
},
"tooltip": {
"lightMode": "Passer en mode clair",
"darkMode": "Passer en mode sombre"
},
"contactsPage": {
"title": "Contacts",
"whatsHappening": "Quoi de neuf?",
"addContact": "Ajouter un contact",
"editContact": "Modifier le contact"
},
"button": {
"OK": "OK",
"Cancel": "Annuler",
"signIn": "Se connecter",
"signOut": "Se déconnecter",
"complete": "Achevé",
"save": "Sauvegarder"
},
"label": {
"welcome": "Bienvenue!",
"firstName": "Prénom",
"middleName": "Deuxième nom",
"lastName": "Nom de famille",
"stepX": "Étape {X}"
},
"oAuth": {
"err": {
"failedTitle": "Incapable de se connecter à votre compte.",
"failedMsg": "SVP vous assurrez d'avoir complèté le processus d'enregistrement dans votre fureteur."
},
"google": {
"title": "S'identifier avec Google",
"instruction1": "Pour importer vos contacts Google, vous devez autoriser cette application à l'aide de votre navigateur Web.",
"instruction2": "Copiez ce code dans votre presse-papiers en cliquant sur l'icône ou en sélectionnant le texte:",
"instruction3": "Accédez au lien suivant dans votre navigateur Web et saisissez le code ci-dessus:",
"instruction4": "Appuyez sur le bouton ci-dessous lorsque vous avez terminé votre inscription:"
}
},
"settings": {
"title": "Paramètres",
"menu": {
"appearance": "Apparence",
"language": "Langue",
"open": "Ouvrir les paramètres"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Mode clair",
"dark": "Mode sombre",
"system": "Adapt to System"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,239 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Moi",
"welcomeText": "Bienvenue sur @:appName",
"githubStarText": "Favoriser sur GitHub",
"subscribeNewsletterText": "S'inscrire à la Newsletter",
"letsGoButtonText": "Allons-y",
"title": "Titre",
"signUp": {
"buttonText": "S'inscrire",
"title": "Inscrivez-vous sur @:appName",
"getStartedText": "Commencer",
"emptyPasswordError": "Vous n'avez pas saisi votre mot de passe",
"repeatPasswordEmptyError": "Vous n'avez pas ressaisi votre mot de passe",
"unmatchedPasswordError": "Les deux mots de passe ne sont pas identiques",
"alreadyHaveAnAccount": "Avez-vous déjà un compte ?",
"emailHint": "Email",
"passwordHint": "Mot de passe",
"repeatPasswordHint": "Ressaisir votre mot de passe"
},
"signIn": {
"loginTitle": "Connexion à @:appName",
"loginButtonText": "Connexion",
"buttonText": "Se connecter",
"forgotPassword": "Mot de passe oublié ?",
"emailHint": "Email",
"passwordHint": "Mot de passe",
"dontHaveAnAccount": "Vous n'avez pas encore créé votre compte ?",
"repeatPasswordEmptyError": "Vous n'avez pas ressaisi votre mot de passe",
"unmatchedPasswordError": "Les deux mots de passe ne sont pas identiques"
},
"workspace": {
"create": "Créer un espace de travail",
"hint": "Espace de travail",
"notFoundError": "Espace de travail introuvable"
},
"shareAction": {
"buttonText": "Partager",
"workInProgress": "Bientôt disponible",
"markdown": "Markdown",
"copyLink": "Copier le lien"
},
"disclosureAction": {
"rename": "Renommer",
"delete": "Supprimer",
"duplicate": "Dupliquer"
},
"blankPageTitle": "Page vierge",
"newPageText": "Nouvelle page",
"trash": {
"text": "Corbeille",
"restoreAll": "Restaurer tout",
"deleteAll": "Supprimer tout",
"pageHeader": {
"fileName": "Nom de fichier",
"lastModified": "Dernière modification",
"created": "Créé"
}
},
"deletePagePrompt": {
"text": "Cette page a été supprimée, vous pouvez la retrouver dans la corbeille",
"restore": "Restaurer la page",
"deletePermanent": "Supprimer définitivement"
},
"dialogCreatePageNameHint": "Nom de la page",
"questionBubble": {
"whatsNew": "Nouveautés",
"help": "Aide et Support",
"debug": {
"name": "Informations de Débogage",
"success": "Informations de Débogage copiées dans le presse-papiers !",
"fail": "Impossible de copier les informations de Débogage dans le presse-papiers"
}
},
"menuAppHeader": {
"addPageTooltip": "Ajoutez rapidement une page à l'intérieur",
"defaultNewPageName": "Sans-titre",
"renameDialog": "Renommer"
},
"toolbar": {
"undo": "Annuler",
"redo": "Rétablir",
"bold": "Gras",
"italic": "Italique",
"underline": "Souligner",
"strike": "Barré",
"numList": "Liste numérotée",
"bulletList": "Liste à puces",
"checkList": "To-Do List",
"inlineCode": "Code",
"quote": "Bloc de citation",
"header": "En-tête",
"highlight": "Surligner"
},
"tooltip": {
"lightMode": "Passer en mode clair",
"darkMode": "Passer en mode sombre",
"openAsPage": "Ouvrir en tant que page",
"addNewRow": "Ajouter une ligne",
"openMenu": "Cliquer pour ouvrir le menu"
},
"sideBar": {
"closeSidebar": "Fermer le menu latéral",
"openSidebar": "Ouvrir le menu latéral"
},
"notifications": {
"export": {
"markdown": "Note exportée en Markdown",
"path": "Documents/flowy"
}
},
"contactsPage": {
"title": "Contacts",
"whatsHappening": "Que se passe-t-il cette semaine ?",
"addContact": "Ajouter un contact",
"editContact": "Modifier le contact"
},
"button": {
"OK": "OK",
"Cancel": "Annuler",
"signIn": "Se connecter",
"signOut": "Se déconnecter",
"complete": "Achevé",
"save": "Enregistrer"
},
"label": {
"welcome": "Bienvenue !",
"firstName": "Prénom",
"middleName": "Deuxième prénom",
"lastName": "Nom",
"stepX": "Étape {X}"
},
"oAuth": {
"err": {
"failedTitle": "Impossible de se connecter à votre compte.",
"failedMsg": "Assurez-vous d'avoir terminé le processus de connexion dans votre navigateur."
},
"google": {
"title": "CONNEXION VIA GOOGLE",
"instruction1": "Pour importer vos contacts Google, vous devez autoriser cette application à l'aide de votre navigateur web.",
"instruction2": "Copiez ce code dans votre presse-papiers en cliquant sur l'icône ou en sélectionnant le texte:",
"instruction3": "Accédez au lien suivant dans votre navigateur web et saisissez le code ci-dessus:",
"instruction4": "Appuyez sur le bouton ci-dessous lorsque vous avez terminé votre inscription:"
}
},
"settings": {
"title": "Paramètres",
"menu": {
"appearance": "Apparence",
"language": "Langue",
"user": "Utilisateur",
"open": "Ouvrir les paramètres"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Mode clair",
"dark": "Mode sombre",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "Filtrer",
"sortBy": "Filtrer par",
"Properties": "Propriétés",
"group": "Groupe"
},
"field": {
"hide": "Cacher",
"insertLeft": "Insérer à gauche",
"insertRight": "Insérer à droite",
"duplicate": "Dupliquer",
"delete": "Supprimer",
"textFieldName": "Texte",
"checkboxFieldName": "Case à cocher",
"dateFieldName": "Date",
"numberFieldName": "Nombre",
"singleSelectFieldName": "Sélectionner",
"multiSelectFieldName": "Multisélection",
"urlFieldName": "URL",
"numberFormat": "Format du nombre",
"dateFormat": "Format de la date",
"includeTime": "Inclure l'heure",
"dateFormatFriendly": "Mois Jour, Année",
"dateFormatISO": "Année-Mois-Jour",
"dateFormatLocal": "Mois/Jour/Année",
"dateFormatUS": "Année/Mois/Jour",
"timeFormat": "Format du temps",
"invalidTimeFormat": "Format invalide",
"timeFormatTwelveHour": "12 heures",
"timeFormatTwentyFourHour": "24 heures",
"addSelectOption": "Ajouter une option",
"optionTitle": "Options",
"addOption": "Ajouter une option",
"editProperty": "Modifier la propriété",
"newProperty": "Nouvelle colonne",
"deleteFieldPromptMessage": "Vous voulez supprimer cette propriété ?"
},
"row": {
"duplicate": "Dupliquer",
"delete": "Supprimer",
"textPlaceholder": "Vide",
"copyProperty": "Copie de la propriété dans le presse-papiers",
"count": "Nombre",
"newRow": "Nouvelle ligne"
},
"selectOption": {
"create": "Créer",
"purpleColor": "Violet",
"pinkColor": "Rose",
"lightPinkColor": "Rose clair",
"orangeColor": "Orange",
"yellowColor": "Jaune",
"limeColor": "Citron vert",
"greenColor": "Vert",
"aquaColor": "Aqua",
"blueColor": "Bleu",
"deleteTag": "Supprimer l'étiquette",
"colorPanelTitle": "Couleurs",
"panelTitle": "Sélectionnez une option ou créez-en une",
"searchOption": "Rechercher une option"
},
"menuName": "Grille"
},
"document": {
"menuName": "Document",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
"column": {
"create_new_card": "Nouveau"
}
}
}
@@ -1,153 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Én",
"welcomeText": "Üdvözöl az @:appName",
"githubStarText": "GitHub csillagozás",
"subscribeNewsletterText": "Iratkozz fel a hírlevelünkre",
"letsGoButtonText": "Vágjunk bele",
"title": "Cím",
"signUp": {
"buttonText": "Regisztráció",
"title": "Regisztrálj az @:appName -ra",
"getStartedText": "Kezdés",
"emptyPasswordError": "A jelszó nem lehet üres",
"repeatPasswordEmptyError": "A jelszó megerősítése nem lehet üres",
"unmatchedPasswordError": "A jelszavak nem egyeznek",
"alreadyHaveAnAccount": "Rendelkezel már fiókkal?",
"emailHint": "Email",
"passwordHint": "Jelszó",
"repeatPasswordHint": "Jelszó megerősítése"
},
"signIn": {
"loginTitle": "Bejelentkezés az @:appName -ba",
"loginButtonText": "Belépés",
"buttonText": "Bejelentkezés",
"forgotPassword": "Elfelejtett jelszó?",
"emailHint": "Email",
"passwordHint": "Jelszó",
"dontHaveAnAccount": "Még nincs fiókod?",
"repeatPasswordEmptyError": "A jelszó megerősítése nem lehet üres",
"unmatchedPasswordError": "A jelszavak nem egyeznek"
},
"workspace": {
"create": "Új munkaterület létrehozása",
"hint": "munkaterület",
"notFoundError": "munkaterület nem található"
},
"shareAction": {
"buttonText": "Megosztás",
"workInProgress": "Hamarosan érkezik...",
"markdown": "Markdown",
"copyLink": "Link másolása"
},
"disclosureAction": {
"rename": "Átnevezés",
"delete": "Törlés",
"duplicate": "Duplikálás"
},
"blankPageTitle": "Üres oldal",
"newPageText": "Új oldal",
"trash": {
"text": "Kuka",
"restoreAll": "Összes visszaállítása",
"deleteAll": "Összes törlése",
"pageHeader": {
"fileName": "Fájlnév",
"lastModified": "Utoljára módosítva",
"created": "Létrehozva"
}
},
"deletePagePrompt": {
"text": "Ez az oldal a kukában van",
"restore": "Oldal visszaállítása",
"deletePermanent": "Végleges törlés"
},
"dialogCreatePageNameHint": "Oldalnév",
"questionBubble": {
"whatsNew": "Újdonságok",
"help": "Segítség & Támogatás",
"debug": {
"name": "Debug Információ",
"success": "Debug információ a vágólapra másolva",
"fail": "A Debug információ nem másolható a vágólapra"
}
},
"menuAppHeader": {
"addPageTooltip": "Belső oldal hozzáadása",
"defaultNewPageName": "Névtelen",
"renameDialog": "Átnevezés"
},
"toolbar": {
"undo": "Vissza",
"redo": "Előre",
"bold": "Félkövér",
"italic": "Dőlt",
"underline": "Aláhúzott",
"strike": "Áthúzott",
"numList": "Számozott lista",
"bulletList": "Felsorolás",
"checkList": "Ellenőrző lista",
"inlineCode": "Inline kód",
"quote": "Idézet",
"header": "Címsor",
"highlight": "Kiemelés"
},
"tooltip": {
"lightMode": "Világos mód",
"darkMode": "Éjjeli mód"
},
"contactsPage": {
"title": "Kontaktok",
"whatsHappening": "Heti újdonságok",
"addContact": "Új Kontakt",
"editContact": "Kontakt Szerkesztése"
},
"button": {
"OK": "OK",
"Cancel": "Mégse",
"signIn": "Bejelentkezés",
"signOut": "Kijelentkezés",
"complete": "Kész",
"save": "Mentés"
},
"label": {
"welcome": "Üdvözlünk!",
"firstName": "Keresztnév",
"middleName": "Középső név",
"lastName": "Vezetéknév",
"stepX": "{X}. lépés"
},
"oAuth": {
"err": {
"failedTitle": "Sikertelen bejelentkezés.",
"failedMsg": "Kérjük győződj meg róla, hogy elvégezted a bejelentkezési folyamatot a böngésződben"
},
"google": {
"title": "Bejelentkezés Google-al",
"instruction1": "Ahhoz, hogy hozzáférj a Google Kontaktjaidhoz, kérjük hatalmazd fel ezt az alkalmazást a böngésződben.",
"instruction2": "Másold ezt a kódot a vágólapra az ikonra kattintással vagy a szöveg kijelölésével:",
"instruction3": "Nyisd meg ezt a linket a böngésződben, és írjd be a fenti kódot:",
"instruction4": "Nyomd meg az alábbi gombot, ha elvégezted a registrációt:"
}
},
"settings": {
"title": "Beállítások",
"menu": {
"appearance": "Megjelenés",
"language": "Nyelv",
"open": "Beállítások megnyitása"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Világos mód",
"dark": "Éjjeli mód",
"system": "Adapt to System"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,226 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Saya",
"welcomeText": "Selamat datang di @:appName",
"githubStarText": "Bintangi GitHub",
"subscribeNewsletterText": "Berlangganan buletin",
"letsGoButtonText": "Ayo",
"title": "Judul",
"signUp": {
"buttonText": "Daftar",
"title": "Daftar ke @:appName",
"getStartedText": "Mulai",
"emptyPasswordError": "Sandi tidak boleh kosong",
"repeatPasswordEmptyError": "Sandi ulang tidak boleh kosong",
"unmatchedPasswordError": "Sandi ulang tidak sama dengan sandi",
"alreadyHaveAnAccount": "Sudah punya akun?",
"emailHint": "Email",
"passwordHint": "Sandi",
"repeatPasswordHint": "Sandi ulang"
},
"signIn": {
"loginTitle": "Masuk ke @:appName",
"loginButtonText": "Masuk",
"buttonText": "Masuk",
"forgotPassword": "Lupa Sandi?",
"emailHint": "Email",
"passwordHint": "Sandi",
"dontHaveAnAccount": "Belum punya akun?",
"repeatPasswordEmptyError": "Sandi ulang tidak boleh kosong",
"unmatchedPasswordError": "Sandi ulang tidak sama dengan sandi"
},
"workspace": {
"create": "Buat workspace",
"hint": "workspace",
"notFoundError": "Workspace tidak ditemukan"
},
"shareAction": {
"buttonText": "Bagikan",
"workInProgress": "Segera",
"markdown": "Markdown",
"copyLink": "Salin tautan"
},
"disclosureAction": {
"rename": "Ganti nama",
"delete": "Hapus",
"duplicate": "Duplikat"
},
"blankPageTitle": "Halaman kosong",
"newPageText": "Halaman baru",
"trash": {
"text": "Sampah",
"restoreAll": "Pulihkan Semua",
"deleteAll": "Hapus semua",
"pageHeader": {
"fileName": "Nama file",
"lastModified": "Terakhir diubah",
"created": "Dibuat"
}
},
"deletePagePrompt": {
"text": "Halaman ini di tempat sampah",
"restore": "Pulihkan halaman",
"deletePermanent": "Hapus secara permanen"
},
"dialogCreatePageNameHint": "Nama halaman",
"questionBubble": {
"whatsNew": "Apa yang baru?",
"help": "Bantuan & Dukungan",
"debug": {
"name": "Info debug",
"success": "Info debug disalin ke papan klip!",
"fail": "Tidak dapat menyalin info debug ke papan klip"
}
},
"menuAppHeader": {
"addPageTooltip": "Menambahkan halaman di dalam dengan cepat",
"defaultNewPageName": "Tanpa Judul",
"renameDialog": "Ganti nama"
},
"toolbar": {
"undo": "Undo",
"redo": "Redo",
"bold": "Tebal",
"italic": "Miring",
"underline": "Garis bawah",
"strike": "Dicoret",
"numList": "Daftar bernomor",
"bulletList": "Daftar berpoin",
"checkList": "Daftar periksa",
"inlineCode": "Kode sebaris",
"quote": "Blok kutipan",
"header": "Tajuk",
"highlight": "Sorotan"
},
"tooltip": {
"lightMode": "Ganti mode terang",
"darkMode": "Ganti mode gelap"
},
"notifications": {
"export": {
"markdown": "Mengekspor Catatan ke Markdown",
"path": "Documents/flowy"
}
},
"contactsPage": {
"title": "Kontak",
"whatsHappening": "Apa yang terjadi minggu ini?",
"addContact": "Tambahkan Kontak",
"editContact": "Ubah Kontak"
},
"button": {
"OK": "Ya",
"Cancel": "Batal",
"signIn": "Masuk",
"signOut": "Keluar",
"complete": "Selesai",
"save": "Simpan"
},
"label": {
"welcome": "Selamat datang!",
"firstName": "Nama Depan",
"middleName": "Nama Tengah",
"lastName": "Nama Akhir",
"stepX": "Langkah {X}"
},
"oAuth": {
"err": {
"failedTitle": "Tidak dapat terhubung ke akun anda",
"failedMsg": "Mohon pastikan anda menyelesaikan proses pendaftaran pada browser anda."
},
"google": {
"title": "MASUK GOOGLE",
"instruction1": "Untuk mengimpor kontak Google Contacts anda, anda harus mengizinkan aplikasi ini menggunakan browser web anda.",
"instruction2": "Salin kode ini ke papan klip anda dengan cara mengklik ikon atau memilih teks:",
"instruction3": "Arahkan ke tautan berikut di browser web Anda, dan masukkan kode di atas:",
"instruction4": "Tekan tombol di bawah ini setelah Anda menyelesaikan pendaftaran:"
}
},
"settings": {
"title": "Pengaturan",
"menu": {
"appearance": "Tampilan",
"language": "Bahasa",
"user": "Pengguna",
"open": "Buka Pengaturan"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Mode Terang",
"dark": "Mode Gelap",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "Filter",
"sortBy": "Sortir dengan",
"Properties": "Properti"
},
"field": {
"hide": "Sembunyikan",
"insertLeft": "Sisipkan Kiri",
"insertRight": "Sisipkan Kanan",
"duplicate": "Duplikasi",
"delete": "Hapus",
"textFieldName": "Teks",
"checkboxFieldName": "Kotak Centang",
"dateFieldName": "Tanggal",
"numberFieldName": "Angka",
"singleSelectFieldName": "seleksi",
"multiSelectFieldName": "Multi seleksi",
"urlFieldName": "URL",
"numberFormat": "Format angka",
"dateFormat": "Format tanggal",
"includeTime": "Sertakan waktu",
"dateFormatFriendly": "Bulan Hari, Tahun",
"dateFormatISO": "Tahun-Bulan-Hari",
"dateFormatLocal": "Bulan/Hari/Tahun",
"dateFormatUS": "Tahun/Bulan/Hari",
"timeFormat": "Format waktu",
"invalidTimeFormat": "Format yang tidak valid",
"timeFormatTwelveHour": "12 jam",
"timeFormatTwentyFourHour": "24 jam",
"addSelectOption": "Tambahkan opsi",
"optionTitle": "Opsi",
"addOption": "Tambahkan opsi",
"editProperty": "Ubah properti"
},
"row": {
"duplicate": "Duplikasi",
"delete": "Hapus",
"textPlaceholder": "Kosong",
"copyProperty": "Salin properti ke papan klip"
},
"selectOption": {
"create": "Buat",
"purpleColor": "Ungu",
"pinkColor": "Merah Jambu",
"lightPinkColor": "Merah Jambu Muda",
"orangeColor": "Oranye",
"yellowColor": "Kuning",
"limeColor": "Limau",
"greenColor": "Hijau",
"aquaColor": "Air",
"blueColor": "Biru",
"deleteTag": "Hapus tag",
"colorPanelTitle": "Warna",
"panelTitle": "Pilih opsi atau buat baru",
"searchOption": "Cari opsi"
},
"menuName": "Grid"
},
"document": {
"menuName": "Dokter",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,159 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Me",
"welcomeText": "Benvenuto in @:appName",
"githubStarText": "Vota su GitHub",
"subscribeNewsletterText": "Sottoscrivi la Newsletter",
"letsGoButtonText": "Andiamo",
"title": "Titolo",
"signUp": {
"buttonText": "Registrati",
"title": "Registrati per @:appName",
"getStartedText": "Iniziamo",
"emptyPasswordError": "La password non può essere vuota",
"repeatPasswordEmptyError": "La password ripetuta non può essere vuota",
"unmatchedPasswordError": "La password ripetuta non è uguale alla password",
"alreadyHaveAnAccount": "Hai già un account?",
"emailHint": "Email",
"passwordHint": "Password",
"repeatPasswordHint": "Ripeti password"
},
"signIn": {
"loginTitle": "Accedi a @:appName",
"loginButtonText": "Login",
"buttonText": "Accedi",
"forgotPassword": "Password Dimentica?",
"emailHint": "Email",
"passwordHint": "Password",
"dontHaveAnAccount": "Non hai un account?",
"repeatPasswordEmptyError": "La password ripetuta non può essere vuota",
"unmatchedPasswordError": "La password ripetuta non è uguale alla password"
},
"workspace": {
"create": "Crea spazio di lavoro",
"hint": "spazio di lavoro",
"notFoundError": "Spazio di lavoro non trovato"
},
"shareAction": {
"buttonText": "Condividi",
"workInProgress": "Prossimamente",
"markdown": "Markdown",
"copyLink": "Copia Link"
},
"disclosureAction": {
"rename": "Rinomina",
"delete": "Cancella",
"duplicate": "Duplica"
},
"blankPageTitle": "Pagina vuota",
"newPageText": "Nuova pagina",
"trash": {
"text": "Cestino",
"restoreAll": "Ripristina Tutto",
"deleteAll": "Elimina Tutto",
"pageHeader": {
"fileName": "Nome file",
"lastModified": "Ultima Modifica",
"created": "Creato"
}
},
"deletePagePrompt": {
"text": "Questa pagina è nel Cestino",
"restore": "Ripristina pagina",
"deletePermanent": "Elimina definitivamente"
},
"dialogCreatePageNameHint": "Nome pagina",
"questionBubble": {
"whatsNew": "Cosa c'è di nuovo?",
"help": "Aiuto & Supporto",
"debug": {
"name": "Informazioni di debug",
"success": "Informazioni di debug copiate negli appunti!",
"fail": "Impossibile copiare le informazioni di debug negli appunti"
}
},
"menuAppHeader": {
"addPageTooltip": "Aggiungi velocemente una pagina all'interno",
"defaultNewPageName": "Senza titolo",
"renameDialog": "Rinomina"
},
"toolbar": {
"undo": "Undo",
"redo": "Redo",
"bold": "Grassetto",
"italic": "Italico",
"underline": "Sottolineato",
"strike": "Barrato",
"numList": "Lista numerata",
"bulletList": "Lista a punti",
"checkList": "Lista Controllo",
"inlineCode": "Codice in linea",
"quote": "Cita Blocco",
"header": "intestazione",
"highlight": "Evidenziare"
},
"tooltip": {
"lightMode": "Passa alla modalità Chiara",
"darkMode": "Passa alla modalità Scura"
},
"contactsPage": {
"title": "Contatti",
"whatsHappening": "Cosa accadrà la prossima settimana?",
"addContact": "Aggiungi Contatti",
"editContact": "Modifica Contatti"
},
"button": {
"OK": "OK",
"Cancel": "Annulla",
"signIn": "Accedi",
"signOut": "Esci",
"complete": "Completa",
"save": "Salva"
},
"label": {
"welcome": "Benvenuto!",
"firstName": "Name",
"middleName": "Secondo Name",
"lastName": "Cognome",
"stepX": "Passo {X}"
},
"oAuth": {
"err": {
"failedTitle": "Impossibile collegarsi al tuo account.",
"failedMsg": "Si prega di verificare di aver completato il processo di iscrizione nel tuo browser."
},
"google": {
"title": "GOOGLE SIGN-IN",
"instruction1": "Al fine di importare i tuoi Contatti Google è necessario autorizzare questa applicazione ad utilizzare il tuo beowser web.",
"instruction2": "Copia questo codice negli appunti premendo l'icona o selezionando il testo:",
"instruction3": "Naviga sul seguente link con il tuo browser web e inserisci il codice seguente:",
"instruction4": "Premi il bottone qui sotto quando hai completato l'iscrizione:"
}
},
"settings": {
"title": "Impostazioni",
"menu": {
"appearance": "Aspetto",
"language": "Lingua",
"open": "aprire le impostazioni"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Modalità Chiara",
"dark": "Modalità Scura",
"system": "Adapt to System"
}
}
},
"grid": {
"menuName": "Griglia"
},
"document": {
"menuName": "Documento"
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,207 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "ユーザー",
"welcomeText": "Welcome to @:appName",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "新着情報を受け取る",
"letsGoButtonText": "Let's Go",
"title": "タイトル",
"signUp": {
"buttonText": "新規登録",
"title": "@:appNameに新規登録",
"getStartedText": "はじめる",
"emptyPasswordError": "パスワードを空にはできません",
"repeatPasswordEmptyError": "パスワード(確認用)を空にはできません",
"unmatchedPasswordError": "パスワード(確認用)が一致しません",
"alreadyHaveAnAccount": "すでにアカウントを登録済ですか?",
"emailHint": "メールアドレス",
"passwordHint": "パスワード",
"repeatPasswordHint": "パスワード(確認用)"
},
"signIn": {
"loginTitle": "@:appName にログイン",
"loginButtonText": "ログイン",
"buttonText": "サインイン",
"forgotPassword": "パスワードをお忘れですか?",
"emailHint": "メールアドレス",
"passwordHint": "パスワード",
"dontHaveAnAccount": "まだアカウントをお持ちではないですか?",
"repeatPasswordEmptyError": "パスワード(確認用)を空にはできません",
"unmatchedPasswordError": "パスワード(確認用)が一致しません"
},
"workspace": {
"create": "ワークスペースを作成する",
"hint": "ワークスペース",
"notFoundError": "ワークスペースがみつかりません"
},
"shareAction": {
"buttonText": "共有する",
"workInProgress": "Coming soon",
"markdown": "Markdown",
"copyLink": "Copy Link"
},
"disclosureAction": {
"rename": "名前を変更",
"delete": "削除",
"duplicate": "コピーを作成"
},
"blankPageTitle": "空のページ",
"newPageText": "新しいページ",
"trash": {
"text": "ごみ箱",
"restoreAll": "全て復元",
"deleteAll": "全て削除",
"pageHeader": {
"fileName": "ファイル名",
"lastModified": "最終更新日時",
"created": "作成日時"
}
},
"deletePagePrompt": {
"text": "このページはごみ箱にあります",
"restore": "ページを元に戻す",
"deletePermanent": "削除する"
},
"dialogCreatePageNameHint": "ページ名",
"questionBubble": {
"whatsNew": "What's new?",
"help": "ヘルプとサポート",
"debug": {
"name": "デバッグ情報",
"success": "デバッグ情報をクリップボードにコピーしました!",
"fail": "デバッグ情報をクリップボードにコピーできませんでした"
}
},
"menuAppHeader": {
"addPageTooltip": "内部ページを追加",
"defaultNewPageName": "Untitled",
"renameDialog": "名前を変更"
},
"toolbar": {
"undo": "元に戻す",
"redo": "やり直し",
"bold": "太字",
"italic": "斜体",
"underline": "下線",
"strike": "取り消し線",
"numList": "番号付きリスト",
"bulletList": "箇条書き",
"checkList": "チェックボックス",
"inlineCode": "インラインコード",
"quote": "引用文",
"header": "見出し",
"highlight": "文字の背景色"
},
"tooltip": {
"lightMode": "ライトモードに切り替える",
"darkMode": "ダークモードに切り替える"
},
"contactsPage": {
"title": "連絡先",
"whatsHappening": "今週はどんなことがありましたか?",
"addContact": "連絡先を追加する",
"editContact": "連絡先を編集する"
},
"button": {
"OK": "OK",
"Cancel": "キャンセル",
"signIn": "サインイン",
"signOut": "サインアウト",
"complete": "完了",
"save": "保存"
},
"label": {
"welcome": "ようこそ!",
"firstName": "名",
"middleName": "ミドルネーム",
"lastName": "姓",
"stepX": "Step {X}"
},
"oAuth": {
"err": {
"failedTitle": "アカウントに接続できません",
"failedMsg": "サインインが完了したことをブラウザーで確認してください"
},
"google": {
"title": "GOOGLEでサインイン",
"instruction1": "GOOGLEでのサインインを有効にするためには、Webブラウザーを使ってこのアプリケーションを認証する必要があります。",
"instruction2": "アイコンをクリックするか、以下のテキストを選択して、このコードをクリップボードにコピーします。",
"instruction3": "以下のリンク先をブラウザーで開いて、次のコードを入力します。",
"instruction4": "登録が完了したら以下のボタンを押してください。"
}
},
"settings": {
"title": "設定",
"menu": {
"appearance": "外観",
"language": "言語",
"open": "設定"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "ライトモード",
"dark": "ダークモード",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "絞り込み",
"sortBy": "並び替え",
"Properties": "プロパティ"
},
"field": {
"hide": "隠す",
"insertLeft": "左に挿入",
"insertRight": "右に挿入",
"duplicate": "コピーを作成",
"delete": "削除",
"textFieldName": "テキスト",
"checkboxFieldName": "チェックボックス",
"dateFieldName": "日付",
"numberFieldName": "数値",
"singleSelectFieldName": "単一選択",
"multiSelectFieldName": "複数選択",
"numberFormat": "数値書式",
"dateFormat": "日付書式",
"includeTime": "時刻を含める",
"dateFormatFriendly": "月 日, 年",
"dateFormatISO": "年-月-日",
"dateFormatLocal": "月/日/年",
"dateFormatUS": "年/月/日",
"timeFormat": "時刻書式",
"timeFormatTwelveHour": "12 時間表記",
"timeFormatTwentyFourHour": "24 時間表記",
"addSelectOption": "選択候補追加",
"optionTitle": "選択候補",
"addOption": "選択候補追加",
"editProperty": "プロパティの編集"
},
"row": {
"duplicate": "コピーを作成",
"delete": "削除",
"textPlaceholder": "空白"
},
"selectOption": {
"purpleColor": "紫",
"pinkColor": "ピンク",
"lightPinkColor": "ライトピンク",
"orangeColor": "オレンジ",
"yellowColor": "黄色",
"limeColor": "ライム",
"greenColor": "緑",
"aquaColor": "水色",
"blueColor": "青",
"deleteTag": "選択候補を削除",
"colorPanelTitle": "色",
"panelTitle": "選択候補を検索 または 作成する",
"searchOption": "選択候補を検索"
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,235 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Me",
"welcomeText": "@:appName 에 오신것을 환영합니다",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "뉴스레터 구독",
"letsGoButtonText": "Let's Go",
"title": "제목",
"signUp": {
"buttonText": "회원가입",
"title": "@:appName 에 회원가입",
"getStartedText": "시작하기",
"emptyPasswordError": "비밀번호는 공백일 수 없습니다",
"repeatPasswordEmptyError": "비밀번호 재입력란은 공백일 수 없습니다",
"unmatchedPasswordError": "재입력 하신 비밀번호가 같지 않습니다",
"alreadyHaveAnAccount": "이미 계정이 있으신가요?",
"emailHint": "이메일",
"passwordHint": "비밀번호",
"repeatPasswordHint": "비밀번호 재입력"
},
"signIn": {
"loginTitle": "@:appName 에 로그인",
"loginButtonText": "로그인",
"buttonText": "로그인",
"forgotPassword": "비밀번호를 잊으셨나요?",
"emailHint": "이메일",
"passwordHint": "비밀번호",
"dontHaveAnAccount": "계정이 없으신가요?",
"repeatPasswordEmptyError": "비밀번호 재입력란은 공백일 수 없습니다",
"unmatchedPasswordError": "재입력 하신 비밀번호가 같지 않습니다"
},
"workspace": {
"create": "워크스페이스 생성",
"hint": "워크스페이스",
"notFoundError": "워크스페이스를 찾을 수 없습니다"
},
"shareAction": {
"buttonText": "공유",
"workInProgress": "Coming soon",
"markdown": "마크다운",
"copyLink": "링크 복사"
},
"disclosureAction": {
"rename": "이름변경",
"delete": "삭제",
"duplicate": "복제"
},
"blankPageTitle": "빈 페이지",
"newPageText": "새로운 페이지",
"trash": {
"text": "휴지통",
"restoreAll": "모두 복구",
"deleteAll": "모두 삭제",
"pageHeader": {
"fileName": "파일 이름",
"lastModified": "수정날짜",
"created": "생성날짜"
}
},
"deletePagePrompt": {
"text": "현재 페이지는 휴지통에 있습니다",
"restore": "페이지 복구",
"deletePermanent": "영구 삭제"
},
"dialogCreatePageNameHint": "페이지 이름",
"questionBubble": {
"whatsNew": "새로운 소식",
"help": "도움 및 지원",
"debug": {
"name": "디버그 정보",
"success": "디버그 정보를 클립보드로 복사했습니다.",
"fail": "디버그 정보를 클립보드로 복사할 수 없습니다."
}
},
"menuAppHeader": {
"addPageTooltip": "하위에 페이지 추가",
"defaultNewPageName": "제목없음",
"renameDialog": "이름변경"
},
"toolbar": {
"undo": "실행취소",
"redo": "재실행",
"bold": "굵게",
"italic": "기울임꼴",
"underline": "밑줄",
"strike": "취소선",
"numList": "번호 매기기 목록",
"bulletList": "글머리 기호 목록",
"checkList": "작업 목록",
"inlineCode": "인라인 코드",
"quote": "인용구 블록",
"header": "헤더",
"highlight": "하이라이트"
},
"tooltip": {
"lightMode": "라이트 모드로 변경",
"darkMode": "다크 모드로 변경",
"openAsPage": "페이지로 열기",
"addNewRow": "열 추가",
"openMenu": "메뉴를 여시려면 클릭하세요"
},
"sideBar": {
"closeSidebar": "사이드바 닫기",
"openSidebar": "사이드바 열기"
},
"notifications": {
"export": {
"markdown": "마크다운으로 노트를 내보냄",
"path": "Documents/flowy"
}
},
"contactsPage": {
"title": "연락처",
"whatsHappening": "이번주에는 무슨 일이 있나요?",
"addContact": "연락처 추가",
"editContact": "연락처 편집"
},
"button": {
"OK": "확인",
"Cancel": "취소",
"signIn": "로그인",
"signOut": "로그아웃",
"complete": "완료",
"save": "저장"
},
"label": {
"welcome": "환영합니다!",
"firstName": "이름",
"middleName": "중간 이름",
"lastName": "성",
"stepX": "{X} 단계"
},
"oAuth": {
"err": {
"failedTitle": "계정에 연결을 할 수 없습니다.",
"failedMsg": "브라우저에서 회원가입이 완료되었는지 확인해주세요."
},
"google": {
"title": "GOOGLE SIGN-IN",
"instruction1": "구글 연락처를 가져오기 위해서 웹브라우저로 앱을 승인 해야 합니다.",
"instruction2": "아이콘을 클릭 또는 텍스트를 선택해서 이 코드를 클립보드로 복사하세요:",
"instruction3": "웹브라우저로 다음 링크로 가셔서 위 코드를 입력해주세요:",
"instruction4": "가입 완료 후 아래 버튼을 눌러주세요:"
}
},
"settings": {
"title": "설정",
"menu": {
"appearance": "화면",
"language": "언어",
"user": "사용자",
"open": "설정 열기"
},
"appearance": {
"lightLabel": "라이트 모드",
"darkLabel": "다크 모드"
}
},
"grid": {
"settings": {
"filter": "필터",
"sortBy": "정렬 기준",
"Properties": "속성",
"group": "그룹"
},
"field": {
"hide": "숨기기",
"insertLeft": "왼쪽 삽입",
"insertRight": "오른쪽 삽입",
"duplicate": "복제",
"delete": "삭제",
"textFieldName": "텍스트",
"checkboxFieldName": "체크박스",
"dateFieldName": "날짜",
"numberFieldName": "숫자",
"singleSelectFieldName": "선택",
"multiSelectFieldName": "다중선택",
"urlFieldName": "링크",
"numberFormat": "숫자 형식",
"dateFormat": "날짜 형식",
"includeTime": "시간 표시",
"dateFormatFriendly": "월 일, 년",
"dateFormatISO": "년-월-일",
"dateFormatLocal": "월/일/년",
"dateFormatUS": "년/월/일",
"timeFormat": "시간 형식",
"invalidTimeFormat": "잘못된 형식",
"timeFormatTwelveHour": "12 시간",
"timeFormatTwentyFourHour": "24 시간",
"addSelectOption": "옵션 추가",
"optionTitle": "옵션",
"addOption": "옵션 추가",
"editProperty": "속성 편집",
"newProperty": "열 추가",
"deleteFieldPromptMessage": "해당 속성을 삭제 하시겠습니까?"
},
"row": {
"duplicate": "복제",
"delete": "삭제",
"textPlaceholder": "비어있음",
"copyProperty": "속성이 클립보드로 복사됨",
"count": "개수",
"newRow": "행 추가"
},
"selectOption": {
"create": "생성",
"purpleColor": "보라색",
"pinkColor": "핑크색",
"lightPinkColor": "연한 핑크색",
"orangeColor": "오렌지색",
"yellowColor": "노랑색",
"limeColor": "라임색",
"greenColor": "초록색",
"aquaColor": "아쿠아색",
"blueColor": "파랑색",
"deleteTag": "태그 삭제",
"colorPanelTitle": "색상",
"panelTitle": "옵션 선택 또는 생성",
"searchOption": "옵션 검색"
},
"menuName": "그리드"
},
"document": {
"menuName": "도큐먼트",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
"column": {
"create_new_card": "추가"
}
}
}
@@ -1,153 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Ja",
"welcomeText": "Witaj w @:appName",
"githubStarText": "Gwiazdka na GitHub-ie",
"subscribeNewsletterText": "Zapisz się do naszego Newslettera",
"letsGoButtonText": "Start!",
"title": "Tytuł",
"signUp": {
"buttonText": "Zarejestruj",
"title": "Zarejestruj się w @:appName",
"getStartedText": "Zaczynamy",
"emptyPasswordError": "Hasło nie moze być puste",
"repeatPasswordEmptyError": "Powtórzone hasło nie moze być puste",
"unmatchedPasswordError": "Hasła nie są takie same",
"alreadyHaveAnAccount": "Masz juz konto?",
"emailHint": "Email",
"passwordHint": "Hasło",
"repeatPasswordHint": "Powtórz hasło"
},
"signIn": {
"loginTitle": "Zaloguj do @:appName",
"loginButtonText": "Logowanie",
"buttonText": "Zaloguj",
"forgotPassword": "Zapomniałem hasła?",
"emailHint": "Email",
"passwordHint": "Password",
"dontHaveAnAccount": "Nie masz konta?",
"repeatPasswordEmptyError": "Powtórzone hasło nie moze być puste",
"unmatchedPasswordError": "Hasła nie są takie same"
},
"workspace": {
"create": "Utwórz przestrzeń",
"hint": "przestrzeń robocza",
"notFoundError": "Przestrzeni nie znaleziono"
},
"shareAction": {
"buttonText": "Udostępnij",
"workInProgress": "Wkrótce",
"markdown": "Markdown",
"copyLink": "Skopiuj link"
},
"disclosureAction": {
"rename": "Zmień nazwę",
"delete": "Usuń",
"duplicate": "Duplikuj"
},
"blankPageTitle": "Pusta strona",
"newPageText": "Nowa strona",
"trash": {
"text": "Kosz",
"restoreAll": "Przywróć Wszystko",
"deleteAll": "Usuń Wszystko",
"pageHeader": {
"fileName": "Nazwa Pliku",
"lastModified": "Ostatnio Zmodyfikowano",
"created": "Utworzono"
}
},
"deletePagePrompt": {
"text": "Ta strona jest w Koszu",
"restore": "Przywróć strone",
"deletePermanent": "Usuń bezpowrotnie"
},
"dialogCreatePageNameHint": "Nazwa Strony",
"questionBubble": {
"whatsNew": "What's new?",
"help": "Pomoc & Wsparcie",
"debug": {
"name": "Informacje Debugowania",
"success": "Skopiowano informacje debugowania do schowka!",
"fail": "Nie mozna skopiować informacji debugowania do schowka"
}
},
"menuAppHeader": {
"addPageTooltip": "Szybko dodaj stronę do środka",
"defaultNewPageName": "Brak tytułu",
"renameDialog": "Zmień nazwę"
},
"toolbar": {
"undo": "Cofnij",
"redo": "Powtórz",
"bold": "Pogrubiony",
"italic": "Kursywa",
"underline": "Podkreśl",
"strike": "Przekreśl",
"numList": "Lista Numerowana",
"bulletList": "Lista Punktowana",
"checkList": "Lista Kontrolna",
"inlineCode": "Kod Wbudowany",
"quote": "Blok cytat",
"header": "Nagłówek",
"highlight": "Podświetl"
},
"tooltip": {
"lightMode": "Przełącz w Tryb Jasny",
"darkMode": "Przełącz w Tryb Ciemny"
},
"contactsPage": {
"title": "Kontakty",
"whatsHappening": "Co się dzieje w tym tygodniu?",
"addContact": "Dodaj Kontakt",
"editContact": "Edytuj Kontakt"
},
"button": {
"OK": "OK",
"Cancel": "Anuluj",
"signIn": "Zaloguj",
"signOut": "Wyloguj",
"complete": "Zakończono",
"save": "Zapisz"
},
"label": {
"welcome": "Witaj!",
"firstName": "Imię Pierwsze",
"middleName": "Imię Drugie",
"lastName": "Nazwisko",
"stepX": "Krok {X}"
},
"oAuth": {
"err": {
"failedTitle": "Nie można połączyć się z Twoim kontem.",
"failedMsg": "Upewnij się, że zakończyłeś proces logowania w przeglądarce."
},
"google": {
"title": "LOGOWANIE GOOGLE",
"instruction1": "Aby zaimportować Kontakty Google, musisz autoryzować tę aplikację za pomocą przeglądarki internetowej.",
"instruction2": "Skopiuj ten kod do schowka, klikając ikonę lub zaznaczając tekst:",
"instruction3": "Przejdź do następującego linku w przeglądarce internetowej i wprowadź powyższy kod:",
"instruction4": "Naciśnij poniższy przycisk po zakończeniu rejestracji:"
}
},
"settings": {
"title": "Ustawienia",
"menu": {
"appearance": "Wygląd",
"language": "Język",
"open": "Otwórz Ustawienia"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Tryb Jasny",
"dark": "Tryb Ciemny",
"system": "Adapt to System"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,149 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Me",
"welcomeText": "Bem vindo ao @:appName",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "Inscreve-te ao Newsletter",
"letsGoButtonText": "Bora",
"title": "Título",
"signUp": {
"buttonText": "Inscreve-te",
"title": "Inscreve-te ao @:appName",
"getStartedText": "Começar",
"emptyPasswordError": "A palavra-passe não pode estar em branco.",
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
"unmatchedPasswordError": "As palavras-passes não coincidem.",
"alreadyHaveAnAccount": "Já possuis uma conta?",
"emailHint": "Email",
"passwordHint": "Password",
"repeatPasswordHint": "Confirma a tua password"
},
"signIn": {
"loginTitle": "Entre no @:appName",
"loginButtonText": "Login",
"buttonText": "Entre",
"forgotPassword": "Esqueceste-te da tua palavra-passe?",
"emailHint": "Email",
"passwordHint": "Palavra-passe",
"dontHaveAnAccount": "Não possuis uma conta?",
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
"unmatchedPasswordError": "As palavras-passes não conferem."
},
"workspace": {
"create": "Cria um ambiente de trabalho",
"hint": "ambiente de trabalho",
"notFoundError": "Ambiente de trabalho não encontrada"
},
"shareAction": {
"buttonText": "Partilhar",
"workInProgress": "Em breve",
"markdown": "Markdown",
"copyLink": "Copiar o link"
},
"disclosureAction": {
"rename": "Renomear",
"delete": "Apagar",
"duplicate": "Duplicar"
},
"blankPageTitle": "Página em branco",
"newPageText": "Nova página",
"trash": {
"text": "Lixo",
"restoreAll": "Restaurar todos",
"deleteAll": "Apagar todos",
"pageHeader": {
"fileName": "Nome do ficheiro",
"lastModified": "Última modificação",
"created": "Criado"
}
},
"deletePagePrompt": {
"text": "Esta página está no lixo",
"restore": "Restaurar a página",
"deletePermanent": "Apagar permanentemente"
},
"dialogCreatePageNameHint": "Nome da página",
"questionBubble": {
"whatsNew": "O que há de novo?",
"help": "Ajuda & Suporte",
"debug": {
"name": "Informação de depuração",
"success": "Copiar informação de depuração para o clipboard!",
"fail": "Falha em copiar a informação de depuração para o clipboard"
}
},
"menuAppHeader": {
"addPageTooltip": "Adiciona uma nova página.",
"defaultNewPageName": "Sem título",
"renameDialog": "Renomear"
},
"toolbar": {
"undo": "Desfazer",
"redo": "Refazer",
"bold": "Negrito",
"italic": "Itálico",
"underline": "Sublinhado",
"strike": "Riscado",
"numList": "Lista numerada",
"bulletList": "Lista com marcadores",
"checkList": "Lista de verificação",
"inlineCode": "Embutir código",
"quote": "Citação em bloco",
"header": "Cabeçalho",
"highlight": "Realçar"
},
"tooltip": {
"lightMode": "Mudar para o modo Claro.",
"darkMode": "Mudar para o modo Escuro."
},
"contactsPage": {
"title": "Conctatos",
"whatsHappening": "O que está a acontecer nesta semana?",
"addContact": "Adicionar um conctato",
"editContact": "Editar um conctato"
},
"button": {
"OK": "OK",
"Cancel": "Cancelar",
"signIn": "Entrar",
"signOut": "Sair",
"complete": "Completar",
"save": "Guardar"
},
"label": {
"welcome": "Bem vindo!",
"firstName": "Nome",
"middleName": "Nome do Meio",
"lastName": "Apelido",
"stepX": "Passo {X}"
},
"oAuth": {
"err": {
"failedTitle": "Erro ao conectar à sua conta.",
"failedMsg": "Verifica se concluiste o processo de login no teu navegador."
},
"google": {
"title": "GOOGLE SIGN-IN",
"instruction1": "Para importar os teus Conctatos do Google, tens de autorizar esta aplicação usando o teu navegador web.",
"instruction2": "Copia este código para a tua área de transferências clicando no ícone ou selecionando o texto:",
"instruction3": "Navega até o link a seguir no seu navegador e digite o código acima:",
"instruction4": "Clica no botão abaixo ao concluir a inscrição:"
}
},
"settings": {
"title": "Definições",
"menu": {
"appearance": "Aparência",
"language": "Idioma",
"open": "Abrir as Definições"
},
"appearance": {
"lightLabel": "Modo Claro",
"darkLabel": "Modo Escuro"
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,239 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Jag",
"welcomeText": "Välkommen till @:appName",
"githubStarText": "Stärnmärk på GitHub",
"subscribeNewsletterText": "Prenumerera på nyhetsbrev",
"letsGoButtonText": "Kör igång",
"title": "Namn",
"signUp": {
"buttonText": "Registrera dig",
"title": "Registrera dig på @:appName",
"getStartedText": "Sätt igång",
"emptyPasswordError": "Lösenordet kan inte vara tomt",
"repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
"unmatchedPasswordError": "Upprepat lösenord är inte samma som det första",
"alreadyHaveAnAccount": "Har du redan ett konto?",
"emailHint": "E-post",
"passwordHint": "Lösenord",
"repeatPasswordHint": "Uprepa lösenordet"
},
"signIn": {
"loginTitle": "Logga in till @:appName",
"loginButtonText": "Logga in",
"buttonText": "Registrering",
"forgotPassword": "Glömt lösenordet?",
"emailHint": "E-post",
"passwordHint": "Lösenord",
"dontHaveAnAccount": "Har du inget konto?",
"repeatPasswordEmptyError": "Upprepat lösenord kan inte vara tomt",
"unmatchedPasswordError": "Upprepat lösenord är inte samma som det första"
},
"workspace": {
"create": "Skapa arbetsyta",
"hint": "Arbetsyta",
"notFoundError": "Hittade ingen arbetsyta"
},
"shareAction": {
"buttonText": "Dela",
"workInProgress": "Kommer snart",
"markdown": "Markdown",
"copyLink": "Kopiera länk"
},
"disclosureAction": {
"rename": "Byt namn",
"delete": "Ta bort",
"duplicate": "Klona"
},
"blankPageTitle": "Tom sida",
"newPageText": "Ny sida",
"trash": {
"text": "Skräp",
"restoreAll": "Återställ alla",
"deleteAll": "Ta bort alla",
"pageHeader": {
"fileName": "Filnamn",
"lastModified": "Ändrad",
"created": "Skapad"
}
},
"deletePagePrompt": {
"text": "Denna sida är i skräpmappen",
"restore": "Återställ sida",
"deletePermanent": "Radera permanent"
},
"dialogCreatePageNameHint": "Sidnamn",
"questionBubble": {
"whatsNew": "Vad nytt?",
"help": "Hjälp & Support",
"debug": {
"name": "Felsökningsinfo",
"success": "Kopierade felsökningsinfo till urklipp!",
"fail": "Kunde inte kopiera felsökningsinfo till urklipp"
}
},
"menuAppHeader": {
"addPageTooltip": "Lägg snabbt till en sida inuti",
"defaultNewPageName": "Namnlös",
"renameDialog": "Byt namn"
},
"toolbar": {
"undo": "Ångra",
"redo": "Upprepa",
"bold": "Fet",
"italic": "Kursiv",
"underline": "Understruken",
"strike": "Genomstruken",
"numList": "Numrerad lista",
"bulletList": "Punktlista",
"checkList": "Checklista",
"inlineCode": "Infogad kod",
"quote": "Citatblock",
"header": "Rubrik",
"highlight": "Färgmarkera"
},
"tooltip": {
"lightMode": "Växla till ljust läge",
"darkMode": "Växla till mörkt läge",
"openAsPage": "Öppna som sida",
"addNewRow": "Lägg till ny rad",
"openMenu": "Klicka för att öppna meny"
},
"sideBar": {
"closeSidebar": "Stäng sidofältet",
"openSidebar": "Öppna sidofältet"
},
"notifications": {
"export": {
"markdown": "Exporterade anteckning till Markdown",
"path": "Dokument/flowy"
}
},
"contactsPage": {
"title": "Kontakter",
"whatsHappening": "Vad händer denna vecka?",
"addContact": "Lägg till kontakt",
"editContact": "Redigera kontakt"
},
"button": {
"OK": "OK",
"Cancel": "Avbryt",
"signIn": "Logga in",
"signOut": "Logga ut",
"complete": "Slutfört",
"save": "Spara"
},
"label": {
"welcome": "Välkommen!",
"firstName": "Förnamn",
"middleName": "Mellannamn",
"lastName": "Efternamn",
"stepX": "Steg {X}"
},
"oAuth": {
"err": {
"failedTitle": "Kan inte ansluta till ditt konto.",
"failedMsg": "Tillse att du har slutfört registreringsprocessen i din webbläsare."
},
"google": {
"title": "GOOGLE-inloggning",
"instruction1": "För att kunna importera dina Google-kontakter, måste du auktorisera detta program med hjälp av din webbläsare.",
"instruction2": "Kopiera den här koden till urklipp genom att klicka på ikonen eller genom att markera texten:",
"instruction3": "Gå till följande länk i din webbläsare, och ange ovanstående kod:",
"instruction4": "Tryck på nedanstående knapp när du slutfört registreringen:"
}
},
"settings": {
"title": "Inställningar",
"menu": {
"appearance": "Utseende",
"language": "Språk",
"user": "Användare",
"open": "Öppna inställningarna"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Ljust läge",
"dark": "Mörkt läge",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "Filter",
"sortBy": "Sortera efter",
"Properties": "Egenskaper",
"group": "Grupp"
},
"field": {
"hide": "Dölj",
"insertLeft": "Infoga till vänster",
"insertRight": "Infoga till höger",
"duplicate": "Klona",
"delete": "Ta bort",
"textFieldName": "Text",
"checkboxFieldName": "Checkruta",
"dateFieldName": "Datum",
"numberFieldName": "Siffror",
"singleSelectFieldName": "Välj",
"multiSelectFieldName": "Välj flera",
"urlFieldName": "URL",
"numberFormat": "Sifferformat",
"dateFormat": "Datumformat",
"includeTime": "Inkludera tid",
"dateFormatFriendly": "Månad Dag, År",
"dateFormatISO": "År-Månad-Dag",
"dateFormatLocal": "Månad/Dag/År",
"dateFormatUS": "År/Månad/Dag",
"timeFormat": "Tidsformat",
"invalidTimeFormat": "Ogiltigt format",
"timeFormatTwelveHour": "12-timmars",
"timeFormatTwentyFourHour": "24-timmars",
"addSelectOption": "Lägg till ett alternativ",
"optionTitle": "Alternativ",
"addOption": "Lägg till alternativ",
"editProperty": "Redigera egenskap",
"newProperty": "Ny kolumn",
"deleteFieldPromptMessage": "Är du säker? Denna egenskap kommer att raderas."
},
"row": {
"duplicate": "Klona",
"delete": "Ta bort",
"textPlaceholder": "Tom",
"copyProperty": "Kopierade egenskap till urklipp",
"count": "Antal",
"newRow": "Ny rad"
},
"selectOption": {
"create": "Skapa",
"purpleColor": "Purpur",
"pinkColor": "Rosa",
"lightPinkColor": "Ljusrosa",
"orangeColor": "Orange",
"yellowColor": "Gul",
"limeColor": "Lime",
"greenColor": "Grön",
"aquaColor": "Vatten",
"blueColor": "Blå",
"deleteTag": "Ta bort tagg",
"colorPanelTitle": "Färger",
"panelTitle": "Välj ett alternativ eller skapa ett",
"searchOption": "Sök efter ett alternativ"
},
"menuName": "Tabell"
},
"document": {
"menuName": "Dokument",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
"column": {
"create_new_card": "Nytt"
}
}
}
@@ -1,153 +0,0 @@
{
"appName": "AppFlowy",
"defaultUsername": "Ben",
"welcomeText": "@:appName'e Hoş Geldiniz!",
"githubStarText": "GitHub Yıldızı!",
"subscribeNewsletterText": "Bültene Abone Ol",
"letsGoButtonText": "Hadi başlayalım.",
"title": "Başlık",
"signUp": {
"buttonText": "Kayıt Ol",
"title": "@:appName'e kaydolun.",
"getStartedText": "başlayalım",
"emptyPasswordError": "Parola boş olamaz",
"repeatPasswordEmptyError": "Parola (tekrar) boş olamaz",
"unmatchedPasswordError": "Parolalar eşleşmiyor",
"alreadyHaveAnAccount": "Zaten hesabınız var mı?",
"emailHint": "E-Posta",
"passwordHint": "Parola",
"repeatPasswordHint": "Tekrar parola"
},
"signIn": {
"loginTitle": "@:appName oturum aç",
"loginButtonText": "Giriş",
"buttonText": "Oturum Aç",
"forgotPassword": "Parolanızı mı Unuttunuz?",
"emailHint": "E-Posta",
"passwordHint": "Parola",
"dontHaveAnAccount": "Hesabınız yok mu?",
"repeatPasswordEmptyError": "Parola (tekrar) boş olamaz",
"unmatchedPasswordError": "Parolalar eşleşmiyor"
},
"workspace": {
"create": "Çalışma alanı oluştur",
"hint": "Çalışma alanı",
"notFoundError": "Çalışma alanı bulunamadı"
},
"shareAction": {
"buttonText": "Paylaş",
"workInProgress": "Yakında",
"markdown": "Markdown",
"copyLink": "Link'i Kopyala"
},
"disclosureAction": {
"rename": "Yeniden adlandır",
"delete": "Sil",
"duplicate": "Çoğalt"
},
"blankPageTitle": "Boş sayfa",
"newPageText": "Yeni sayfa",
"trash": {
"text": "Çöp",
"restoreAll": "Geri Yükle",
"deleteAll": "Sil",
"pageHeader": {
"fileName": "Dosya adı",
"lastModified": "Son Değiştirme",
"created": "Oluşturuldu"
}
},
"deletePagePrompt": {
"text": "Bu sayfa Çöp Kutusu'nda",
"restore": "Sayfayı geri yükle",
"deletePermanent": "Kalıcı olarak sil"
},
"dialogCreatePageNameHint": "Sayfa adı",
"questionBubble": {
"whatsNew": "Yeni ne var?",
"help": "Yardım & Destek",
"debug": {
"name": "Hata Ayıklama",
"success": "Hata ayıklama bilgileri panoya kopyalandı!",
"fail": "Hata ayıklama bilgileri panoya kopyalanamıyor"
}
},
"menuAppHeader": {
"addPageTooltip": "Yeni bir sayfa ekleyin",
"defaultNewPageName": "Başlıksız",
"renameDialog": "Yeniden adlandır"
},
"toolbar": {
"undo": "Geri",
"redo": "İleri",
"bold": "Kalın",
"italic": "İtalik",
"underline": "Altı Çizili",
"strike": "Üstü Çizili",
"numList": "Numaralı Liste",
"bulletList": "Madde İşaretli Liste",
"checkList": "Yapılacaklar Listesi",
"inlineCode": "Kod",
"quote": "Alıntı",
"header": "Başlık",
"highlight": "Vurgu"
},
"tooltip": {
"lightMode": "Aydınlık Mod'a Geç",
"darkMode": "Karanlık Mod'a Geç"
},
"contactsPage": {
"title": "İletişim",
"whatsHappening": "Bu hafta neler var?",
"addContact": "Kişi Ekle",
"editContact": "Kişiyi Düzenle"
},
"button": {
"OK": "TAMAM",
"Cancel": "İptal",
"signIn": "Oturum Aç",
"signOut": "Oturum Kapat",
"complete": "Tamamlandı",
"save": "Kaydet"
},
"label": {
"welcome": "Merhaba!",
"firstName": "Ad",
"middleName": "İkinci Ad",
"lastName": "Soyad",
"stepX": "Aşama {X}"
},
"oAuth": {
"err": {
"failedTitle": "Hesabınıza bağlanılamıyor.",
"failedMsg": "Lütfen, tarayıcınızda oturum açma işlemini tamamladığınızdan emin olun."
},
"google": {
"title": "GOOGLE OTURUM AÇMA",
"instruction1": "Google Kişilerinizi içe aktarmak için web tarayıcınızı kullanarak bu uygulamaya izin vermeniz gerekir.",
"instruction2": "Simgeyi tıklayarak veya metni seçerek bu kodu panonuza kopyalayın:",
"instruction3": "Web tarayıcınızda aşağıdaki bağlantıyı açın ve yukarıdaki kodu girin:",
"instruction4": "Kayıt işlemini tamamladığınızda aşağıdaki düğmeye basın:"
}
},
"settings": {
"title": "Ayarlar",
"menu": {
"appearance": "Görünüm",
"language": "Dil",
"open": "Ayarları Aç"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "Aydınlık Mod",
"dark": "Karanlık Mod",
"system": "Adapt to System"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
@@ -1,240 +0,0 @@
{
"appName": "Appflowy",
"defaultUsername": "我",
"welcomeText": "欢迎使用 @:appName",
"githubStarText": "Star on GitHub",
"subscribeNewsletterText": "消息订阅",
"letsGoButtonText": "开始",
"title": "标题",
"signUp": {
"buttonText": "注册",
"title": "注册 @:appName 账户",
"getStartedText": "开始",
"emptyPasswordError": "密码不能为空",
"repeatPasswordEmptyError": "确认密码不能为空",
"unmatchedPasswordError": "两次密码输入不一致",
"alreadyHaveAnAccount": "已有账户?",
"emailHint": "邮箱",
"passwordHint": "密码",
"repeatPasswordHint": "确认密码"
},
"signIn": {
"loginTitle": "登录 @:appName",
"loginButtonText": "登录",
"buttonText": "登录",
"forgotPassword": "忘记密码?",
"emailHint": "邮箱",
"passwordHint": "密码",
"dontHaveAnAccount": "没有已注册的账户?",
"repeatPasswordEmptyError": "确认密码不能为空",
"unmatchedPasswordError": "两次密码输入不一致"
},
"workspace": {
"create": "新建空间",
"hint": "空间",
"notFoundError": "未知的空间"
},
"shareAction": {
"buttonText": "分享",
"workInProgress": "敬请期待",
"markdown": "Markdown",
"copyLink": "复制链接"
},
"disclosureAction": {
"rename": "重命名",
"delete": "删除",
"duplicate": "复制"
},
"blankPageTitle": "空白页",
"newPageText": "新页面",
"trash": {
"text": "回收站",
"restoreAll": "全部恢复",
"deleteAll": "全部删除",
"pageHeader": {
"fileName": "文件名",
"lastModified": "最近修改",
"created": "创建"
}
},
"deletePagePrompt": {
"text": "此页面已被移动至回收站",
"restore": "恢复页面",
"deletePermanent": "彻底删除"
},
"dialogCreatePageNameHint": "页面名称",
"questionBubble": {
"whatsNew": "新功能?",
"help": "帮助 & 支持",
"debug": {
"name": "调试信息",
"success": "将调试信息复制到剪贴板!",
"fail": "无法将调试信息复制到剪贴板"
}
},
"menuAppHeader": {
"addPageTooltip": "在其中快速添加页面",
"defaultNewPageName": "未命名页面",
"renameDialog": "重命名"
},
"toolbar": {
"undo": "撤销",
"redo": "恢复",
"bold": "加粗",
"italic": "斜体",
"underline": "下划线",
"strike": "删除线",
"numList": "有序列表",
"bulletList": "无序列表",
"checkList": "任务列表",
"inlineCode": "内联代码",
"quote": "块引用",
"header": "标题",
"highlight": "高亮"
},
"tooltip": {
"lightMode": "切换到亮色模式",
"darkMode": "切换到暗色模式",
"openAsPage": "作为页面打开",
"addNewRow": "增加一行",
"openMenu": "点击打开菜单"
},
"sideBar": {
"openSidebar": "打开侧边栏",
"closeSidebar": "关闭侧边栏"
},
"notifications": {
"export": {
"markdown": "导出笔记为Markdown文档",
"path": "Documents/flowy"
}
},
"contactsPage": {
"title": "联系人",
"whatsHappening": "这周发生了哪些事?",
"addContact": "添加联系人",
"editContact": "编辑联系人"
},
"button": {
"OK": "确认",
"Cancel": "取消",
"signIn": "登录",
"signOut": "登出",
"complete": "完成",
"save": "保存"
},
"label": {
"welcome": "欢迎!",
"firstName": "名",
"middleName": "中间名",
"lastName": "姓",
"stepX": "第{X}步"
},
"oAuth": {
"err": {
"failedTitle": "无法连接到您的账户。",
"failedMsg": "请确认您已在浏览器中完成登录。"
},
"google": {
"title": "Google 账号登录",
"instruction1": "为了导入您的 Google 联系人,您需要在浏览器中给予本程序授权。",
"instruction2": "单击图标或选择文本复制到剪贴板:",
"instruction3": "进入下面的链接,然后输入上面的代码:",
"instruction4": "完成注册后,点击下面的按钮:"
}
},
"settings": {
"title": "设置",
"menu": {
"appearance": "外观",
"language": "语言",
"user": "用户",
"open": "打开设置"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"light": "日间模式",
"dark": "夜间模式",
"system": "Adapt to System"
}
}
},
"grid": {
"settings": {
"filter": "过滤器",
"sortBy": "排序",
"Properties": "属性",
"group": "组"
},
"field": {
"hide": "隐藏",
"insertLeft": "左侧插入",
"insertRight": "右侧插入",
"duplicate": "拷贝",
"delete": "删除",
"textFieldName": "文本",
"checkboxFieldName": "勾选框",
"dateFieldName": "日期",
"numberFieldName": "数字",
"singleSelectFieldName": "单项选择器",
"multiSelectFieldName": "多项选择器",
"urlFieldName": "链接",
"numberFormat": "数字格式",
"dateFormat": "日期格式",
"includeTime": "包含时间",
"dateFormatFriendly": "月 日, 年",
"dateFormatISO": "年-月-日",
"dateFormatLocal": "月/日/年",
"dateFormatUS": "年/月/日",
"timeFormat": "时间格式",
"invalidTimeFormat": "时间格式错误",
"timeFormatTwelveHour": "12小时制",
"timeFormatTwentyFourHour": "24小时制",
"addSelectOption": "添加一个标签",
"optionTitle": "标签",
"addOption": "添加标签",
"editProperty": "编辑列属性",
"newProperty": "增加一列",
"deleteFieldPromptMessage": "确定要删除这个属性吗? "
},
"row": {
"duplicate": "复制",
"delete": "删除",
"textPlaceholder": "空",
"copyProperty": "复制列",
"count": "数量",
"newRow": "添加一行"
},
"selectOption": {
"create": "新建",
"purpleColor": "紫色",
"pinkColor": "粉色",
"lightPinkColor": "浅粉色",
"orangeColor": "橙色",
"yellowColor": "黄色",
"limeColor": "鲜绿色",
"greenColor": "绿色",
"aquaColor": "水蓝色",
"blueColor": "蓝色",
"deleteTag": "删除标签",
"colorPanelTitle": "颜色",
"panelTitle": "选择或新建一个标签",
"searchOption": "搜索标签"
},
"menuName": "网格"
},
"document": {
"menuName": "文档",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
}
},
"board": {
"column": {
"create_new_card": "新建"
},
"menuName": "看板"
}
}
@@ -0,0 +1,45 @@
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('appearance settings tests', () {
testWidgets('after editing text field, button should be able to be clicked',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeHomePage();
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.appearance);
final dropDown = find.byKey(ThemeFontFamilySetting.popoverKey);
await tester.tap(dropDown);
await tester.pumpAndSettle();
final textField = find.byKey(ThemeFontFamilySetting.textFieldKey);
await tester.tap(textField);
await tester.pumpAndSettle();
await tester.enterText(textField, 'Abel');
await tester.pumpAndSettle();
final fontFamilyButton = find.byKey(const Key('Abel'));
expect(fontFamilyButton, findsOneWidget);
await tester.tap(fontFamilyButton);
await tester.pumpAndSettle();
// just switch the page and verify that the font family was set after that
await tester.openSettingsPage(SettingsPage.files);
await tester.openSettingsPage(SettingsPage.appearance);
expect(find.textContaining('Abel'), findsOneWidget);
});
});
}
@@ -0,0 +1,50 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/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 '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('board row test', () {
testWidgets('delete item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Board);
const name = 'Card 1';
final card1 = find.findTextInFlowyText(name);
await tester.hoverOnWidget(
card1,
onHover: () async {
final moreOption = find.byType(CardMoreOption);
await tester.tapButton(moreOption);
},
);
await tester.tapButtonWithName(LocaleKeys.button_delete.tr());
expect(find.findTextInFlowyText(name), findsNothing);
});
testWidgets('duplicate item in ToDo card', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Board);
const name = 'Card 1';
final card1 = find.findTextInFlowyText(name);
await tester.hoverOnWidget(
card1,
onHover: () async {
final moreOption = find.byType(CardMoreOption);
await tester.tapButton(moreOption);
},
);
await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr());
expect(find.textContaining(name, findRichText: true), findsNWidgets(2));
});
});
}
@@ -0,0 +1,10 @@
import 'package:integration_test/integration_test.dart';
import 'board_row_test.dart' as board_row_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Board integration tests
board_row_test.main();
}
@@ -1,36 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('cover image', () {
const location = 'cover_image';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets(
'hovering on cover image will display change and delete cover image buttons',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.editor.hoverOnCoverPluginAddButton();
tester.expectToSeePluginAddCoverAndIconButton();
});
});
}
@@ -0,0 +1,190 @@
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pbenum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('calendar database view', () {
testWidgets('update calendar layout', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// open setting
await tester.tapDatabaseSettingButton();
await tester.tapDatabaseLayoutButton();
await tester.selectDatabaseLayoutType(DatabaseLayoutPB.Board);
await tester.assertCurrentDatabaseLayoutType(DatabaseLayoutPB.Board);
await tester.tapDatabaseSettingButton();
await tester.tapDatabaseLayoutButton();
await tester.selectDatabaseLayoutType(DatabaseLayoutPB.Grid);
await tester.assertCurrentDatabaseLayoutType(DatabaseLayoutPB.Grid);
await tester.pumpAndSettle();
});
testWidgets('calendar start from day setting', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create calendar view
const name = 'calendar';
await tester.createNewPageWithName(
name: name,
layout: ViewLayoutPB.Calendar,
);
// Open setting
await tester.tapDatabaseSettingButton();
await tester.tapCalendarLayoutSettingButton();
// select the first day of week is Monday
await tester.tapFirstDayOfWeek();
await tester.tapFirstDayOfWeekStartFromMonday();
// Open the other page and open the new calendar page again
await tester.openPage(gettingStated);
await tester.pumpAndSettle(const Duration(milliseconds: 300));
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
// Open setting again and check the start from Monday is selected
await tester.tapDatabaseSettingButton();
await tester.tapCalendarLayoutSettingButton();
await tester.tapFirstDayOfWeek();
tester.assertFirstDayOfWeekStartFromMonday();
await tester.pumpAndSettle();
});
testWidgets('creating and editing calendar events', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create the calendar view
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Scroll until today's date cell is visible
await tester.scrollToToday();
// Hover over today's calendar cell
await tester.hoverOnTodayCalendarCell();
// Tap on create new event button
await tester.tapAddCalendarEventButton();
// Make sure that the row details page is opened
tester.assertRowDetailPageOpened();
// Dismiss the row details page
await tester.dismissRowDetailPage();
tester.assertNumberOfEventsInCalendar(1);
// Double click on today's calendar cell to create a new event
await tester.doubleClickCalendarCell(DateTime.now());
// Make sure that the row details page is opened
tester.assertRowDetailPageOpened();
// Dismiss the row details page
await tester.dismissRowDetailPage();
// Make sure that the event is inserted in the cell
tester.assertNumberOfEventsInCalendar(2);
// Click on the event
await tester.openCalendarEvent(index: 0);
tester.assertRowDetailPageOpened();
// Change the title of the event
await tester.editTitleInRowDetailPage('hello world');
await tester.dismissRowDetailPage();
// Make sure that the event is edited
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
// Click on the event
await tester.openCalendarEvent(index: 1);
tester.assertRowDetailPageOpened();
// Duplicate the event
await tester.tapRowDetailPageDuplicateRowButton();
await tester.dismissRowDetailPage();
// Check that there are 2 events
tester.assertNumberOfEventsInCalendar(2, title: 'hello world');
tester.assertNumberOfEventsOnSpecificDay(3, DateTime.now());
// Delete an event
await tester.openCalendarEvent(index: 1);
await tester.tapRowDetailPageDeleteRowButton();
// Check that there is 1 event
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
});
testWidgets('rescheduling events', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create the calendar view
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Create a new event on the first of this month
final today = DateTime.now();
final firstOfThisMonth = DateTime(today.year, today.month, 1);
await tester.doubleClickCalendarCell(firstOfThisMonth);
await tester.dismissRowDetailPage();
// Drag and drop the event onto the next week, same day
await tester.dragDropRescheduleCalendarEvent(firstOfThisMonth);
// Make sure that the event has been rescheduled to the new date
final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7));
tester.assertNumberOfEventsInCalendar(1);
tester.assertNumberOfEventsOnSpecificDay(1, sameDayNextWeek);
// Delete the event
await tester.openCalendarEvent(index: 0, date: sameDayNextWeek);
await tester.tapRowDetailPageDeleteRowButton();
// Create a new event in today's calendar cell
await tester.scrollToToday();
await tester.doubleClickCalendarCell(today);
await tester.dismissRowDetailPage();
// Make sure that the event is today
tester.assertNumberOfEventsOnSpecificDay(1, today);
// Click on the event
await tester.openCalendarEvent(index: 0);
// Open the date editor of the event
await tester.tapDateCellInRowDetailPage();
await tester.findDateEditor(findsOneWidget);
// Edit the event's date. To avoid selecting a day outside of the current month, the new date will be one day closer to the middle of the month.
final newDate = today.day < 15
? today.add(const Duration(days: 1))
: today.subtract(const Duration(days: 1));
await tester.selectDay(content: newDate.day);
await tester.dismissCellEditor();
// Dismiss the row details page
await tester.dismissRowDetailPage();
// Make sure that the event is edited
tester.assertNumberOfEventsInCalendar(1);
tester.assertNumberOfEventsOnSpecificDay(1, newDate);
});
});
}
@@ -1,6 +1,8 @@
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:intl/intl.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
@@ -9,27 +11,11 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid cell', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('edit text cell', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.editCell(
rowIndex: 0,
@@ -46,12 +32,52 @@ void main() {
await tester.pumpAndSettle();
});
// Makesure the text cells are filled with the right content when there are
// multiple text cell
testWidgets('edit multiple text cells', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(
name: 'my grid',
layout: ViewLayoutPB.Grid,
);
await tester.createField(FieldType.RichText, 'description');
await tester.editCell(
rowIndex: 0,
fieldType: FieldType.RichText,
input: 'hello',
);
await tester.editCell(
rowIndex: 0,
fieldType: FieldType.RichText,
input: 'world',
cellIndex: 1,
);
await tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.RichText,
content: 'hello',
cellIndex: 0,
);
await tester.assertCellContent(
rowIndex: 0,
fieldType: FieldType.RichText,
content: 'world',
cellIndex: 1,
);
await tester.pumpAndSettle();
});
testWidgets('edit number cell', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.Number;
@@ -109,8 +135,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.assertCheckboxCell(rowIndex: 0, isSelected: false);
await tester.tapCheckboxCellInGrid(rowIndex: 0);
@@ -128,8 +153,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.CreatedTime;
// Create a create time field
@@ -147,8 +171,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.LastEditedTime;
// Create a last time field
@@ -166,8 +189,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.DateTime;
await tester.createField(fieldType, fieldType.name);
@@ -176,8 +198,246 @@ void main() {
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Select the date
await tester.selectDay(content: 3);
// Toggle include time
await tester.toggleIncludeTime();
// Dismiss the cell editor
await tester.dismissCellEditor();
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Turn off include time
await tester.toggleIncludeTime();
// Select a date
final today = DateTime.now();
await tester.selectDay(content: today.day);
await tester.dismissCellEditor();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: DateFormat('MMM dd, y').format(today),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Toggle include time
final now = DateTime.now();
await tester.toggleIncludeTime();
await tester.dismissCellEditor();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: DateFormat('MMM dd, y HH:mm').format(now),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Change date format
await tester.changeDateFormat();
await tester.dismissCellEditor();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: DateFormat('dd/MM/y HH:mm').format(now),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Change time format
await tester.changeTimeFormat();
await tester.dismissCellEditor();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: DateFormat('dd/MM/y hh:mm a').format(now),
);
await tester.tapCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findDateEditor(findsOneWidget);
// Clear the date and time
await tester.clearDate();
await tester.assertDateCellInGrid(
rowIndex: 0,
fieldType: fieldType,
content: '',
);
await tester.pumpAndSettle();
});
testWidgets('edit single select cell', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const fieldType = FieldType.SingleSelect;
// When create a grid, it will create a single select field by default
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Tap the cell to invoke the selection option editor
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Create a new select option
await tester.createOption(name: 'tag 1');
await tester.dismissCellEditor();
// Make sure the option is created and displayed in the cell
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: 'tag 1',
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Create another select option
await tester.createOption(name: 'tag 2');
await tester.dismissCellEditor();
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: 'tag 2',
);
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsOneWidget,
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// switch to first option
await tester.selectOption(name: 'tag 1');
await tester.dismissCellEditor();
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: 'tag 1',
);
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsOneWidget,
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Deselect the currently-selected option
await tester.selectOption(name: 'tag 1');
await tester.dismissCellEditor();
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsNothing,
);
await tester.pumpAndSettle();
});
testWidgets('edit multi select cell', (tester) async {
final tags = [
'tag 1',
'tag 2',
'tag 3',
'tag 4',
];
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.MultiSelect;
await tester.createField(fieldType, fieldType.name);
// Tap the cell to invoke the selection option editor
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Create a new select option
await tester.createOption(name: tags.first);
await tester.dismissCellEditor();
// Make sure the option is created and displayed in the cell
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: tags.first,
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Create some other select options
await tester.createOption(name: tags[1]);
await tester.createOption(name: tags[2]);
await tester.createOption(name: tags[3]);
await tester.dismissCellEditor();
for (final tag in tags) {
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: tag,
);
}
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsNWidgets(4),
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Deselect all options
for (final tag in tags) {
await tester.selectOption(name: tag);
}
await tester.dismissCellEditor();
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsNothing,
);
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
await tester.findSelectOptionEditor(findsOneWidget);
// Select some options
await tester.selectOption(name: tags[1]);
await tester.selectOption(name: tags[3]);
await tester.dismissCellEditor();
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: tags[1],
);
await tester.findSelectOptionWithNameInGrid(
rowIndex: 0,
name: tags[3],
);
await tester.assertNumberOfSelectedOptionsInGrid(
rowIndex: 0,
matcher: findsNWidgets(2),
);
await tester.pumpAndSettle();
});
@@ -1,5 +1,8 @@
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/select_option.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -10,27 +13,11 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid page', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('rename existing field', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Invoke the field editor
await tester.tapGridFieldWithName('Name');
@@ -47,8 +34,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Invoke the field editor
await tester.tapGridFieldWithName('Type');
@@ -70,8 +56,7 @@ void main() {
await tester.tapGoButton();
// create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.createField(FieldType.Checklist, 'checklist');
@@ -85,8 +70,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.createField(FieldType.Checkbox, 'New field 1');
@@ -106,15 +90,13 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
await tester.tapNewPropertyButton();
await tester.renameField('New field 1');
await tester.dismissFieldEditor();
await tester.createField(FieldType.RichText, 'New field 1');
// Delete the field
await tester.tapGridFieldWithName('New field 1');
@@ -128,8 +110,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// create a field
await tester.scrollToRight(find.byType(GridPage));
@@ -149,8 +130,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.scrollToRight(find.byType(GridPage));
await tester.tapNewPropertyButton();
@@ -170,8 +150,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
for (final fieldType in [
FieldType.Checklist,
@@ -198,5 +177,33 @@ void main() {
await tester.pumpAndSettle();
}
});
testWidgets('add option', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(
layout: ViewLayoutPB.Grid,
);
// Invoke the field editor
await tester.tapGridFieldWithName('Type');
await tester.tapEditPropertyButton();
// tap 'add option' button
await tester.tapAddSelectOptionButton();
const text = 'Hello AppFlowy';
final inputField = find.descendant(
of: find.byType(CreateOptionTextField),
matching: find.byType(TextField),
);
await tester.enterText(inputField, text);
await tester.pumpAndSettle();
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle(const Duration(seconds: 1));
// check the result
tester.expectToSeeText(text);
});
});
}
@@ -0,0 +1,142 @@
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/text.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database filter', () {
testWidgets('add text filter', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(FieldType.RichText, 'Name');
await tester.tapFilterButtonInGrid('Name');
// enter 'A' in the filter text field
await tester.assertNumberOfRowsInGridPage(10);
await tester.enterTextInTextFilter('A');
await tester.assertNumberOfRowsInGridPage(1);
// after remove the filter, the grid should show all rows
await tester.enterTextInTextFilter('');
await tester.assertNumberOfRowsInGridPage(10);
await tester.enterTextInTextFilter('B');
await tester.assertNumberOfRowsInGridPage(1);
// open the menu to delete the filter
await tester.tapDisclosureButtonInFinder(find.byType(TextFilterEditor));
await tester.tapDeleteFilterButtonInGrid();
await tester.assertNumberOfRowsInGridPage(10);
await tester.pumpAndSettle();
});
testWidgets('add checkbox filter', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(FieldType.Checkbox, 'Done');
await tester.assertNumberOfRowsInGridPage(5);
await tester.tapFilterButtonInGrid('Done');
await tester.tapCheckboxFilterButtonInGrid();
await tester.tapUnCheckedButtonOnCheckboxFilter();
await tester.assertNumberOfRowsInGridPage(5);
await tester
.tapDisclosureButtonInFinder(find.byType(CheckboxFilterEditor));
await tester.tapDeleteFilterButtonInGrid();
await tester.assertNumberOfRowsInGridPage(10);
await tester.pumpAndSettle();
});
testWidgets('add checklist filter', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(FieldType.Checklist, 'checklist');
// By default, the condition of checklist filter is 'uncompleted'
await tester.assertNumberOfRowsInGridPage(9);
await tester.tapFilterButtonInGrid('checklist');
await tester.tapChecklistFilterButtonInGrid();
await tester.tapCompletedButtonOnChecklistFilter();
await tester.assertNumberOfRowsInGridPage(1);
await tester.pumpAndSettle();
});
testWidgets('add single select filter', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(FieldType.SingleSelect, 'Type');
await tester.tapFilterButtonInGrid('Type');
// select the option 's6'
await tester.tapOptionFilterWithName('s6');
await tester.assertNumberOfRowsInGridPage(0);
// unselect the option 's6'
await tester.tapOptionFilterWithName('s6');
await tester.assertNumberOfRowsInGridPage(10);
// select the option 's5'
await tester.tapOptionFilterWithName('s5');
await tester.assertNumberOfRowsInGridPage(1);
// select the option 's4'
await tester.tapOptionFilterWithName('s4');
// The row with 's4' or 's5' should be shown.
await tester.assertNumberOfRowsInGridPage(2);
await tester.pumpAndSettle();
});
testWidgets('add multi select filter', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseFilterButton();
await tester.tapCreateFilterByFieldType(
FieldType.MultiSelect,
'multi-select',
);
await tester.tapFilterButtonInGrid('multi-select');
await tester.scrollOptionFilterListByOffset(const Offset(0, -200));
// select the option 'm1'. Any option with 'm1' should be shown.
await tester.tapOptionFilterWithName('m1');
await tester.assertNumberOfRowsInGridPage(5);
await tester.tapOptionFilterWithName('m1');
// select the option 'm2'. Any option with 'm2' should be shown.
await tester.tapOptionFilterWithName('m2');
await tester.assertNumberOfRowsInGridPage(4);
await tester.tapOptionFilterWithName('m2');
// select the option 'm4'. Any option with 'm4' should be shown.
await tester.tapOptionFilterWithName('m4');
await tester.assertNumberOfRowsInGridPage(1);
await tester.pumpAndSettle();
});
});
}
@@ -1,43 +1,32 @@
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/ime.dart';
import 'util/emoji.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('open first row of the grid', (tester) async {
testWidgets('row details page opens', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
// Make sure that the row page is opened
tester.assertRowDetailPageOpened();
});
testWidgets('insert emoji in the row detail page', (tester) async {
@@ -45,8 +34,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -66,8 +54,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -83,7 +70,7 @@ void main() {
// The emoji already displayed in the row banner
final emojiText = find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == '😅',
(widget) => widget is FlowyText && widget.text == '😅',
);
// The number of emoji should be two. One in the row displayed in the grid
@@ -96,8 +83,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -109,7 +95,7 @@ void main() {
// Remove the emoji
await tester.tapButton(find.byType(RemoveEmojiButton));
final emojiText = find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == '😀',
(widget) => widget is FlowyText && widget.text == '😀',
);
expect(emojiText, findsNothing);
});
@@ -119,8 +105,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -155,8 +140,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -171,8 +155,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -183,9 +166,10 @@ void main() {
// Focus on the editor
final textBlock = find.byType(TextBlockComponentWidget);
await tester.tapAt(tester.getCenter(textBlock));
await tester.pumpAndSettle();
// Input some text
const inputText = 'Hello world';
const inputText = 'Hello World';
await tester.ime.insertText(inputText);
expect(
find.textContaining(inputText, findRichText: true),
@@ -204,13 +188,53 @@ void main() {
);
});
testWidgets(
'check if the title wraps properly when a long text is inserted',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create a new grid
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
// Wait for the document to be loaded
await tester.wait(500);
// Focus on the editor
final textField = find
.descendant(
of: find.byType(SimpleDialog),
matching: find.byType(TextField),
)
.first;
// Input a long text
await tester.enterText(textField, 'Long text' * 25);
await tester.pumpAndSettle();
// Tap outside to dismiss the field
await tester.tapAt(Offset.zero);
await tester.pumpAndSettle();
// Check if there is any overflow in the widget tree
expect(tester.takeException(), isNull);
// Re-open the document
await tester.openFirstRowDetailPage();
// Check again if there is any overflow in the widget tree
expect(tester.takeException(), isNull);
});
testWidgets('delete row in row detail page', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -226,8 +250,7 @@ void main() {
await tester.tapGoButton();
// Create a new grid
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Hover first row and then open the row page
await tester.openFirstRowDetailPage();
@@ -1,3 +1,4 @@
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -8,27 +9,11 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('create row of the grid', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.tapCreateRowButtonInGrid();
// The initial number of rows is 3
@@ -40,8 +25,8 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.hoverOnFirstRowOfGrid();
await tester.tapCreateRowButtonInRowMenuOfGrid();
@@ -56,8 +41,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.hoverOnFirstRowOfGrid();
// Open the row menu and then click the delete
@@ -75,8 +59,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.assertRowCountInGridPage(3);
await tester.pumpAndSettle();
@@ -1,4 +1,5 @@
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -9,27 +10,11 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('update layout', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// open setting
await tester.tapDatabaseSettingButton();
@@ -46,8 +31,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// open setting
await tester.tapDatabaseSettingButton();
@@ -0,0 +1,166 @@
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database', () {
testWidgets('import v0.2.0 database data', (tester) async {
await tester.openV020database();
// wait the database data is loaded
await tester.pumpAndSettle(const Duration(microseconds: 500));
// check the text cell
final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];
for (final (index, content) in textCells.indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.RichText,
content: content,
);
}
// check the checkbox cell
final checkboxCells = <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false
];
for (final (index, content) in checkboxCells.indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
// check the number cell
final numberCells = <String>[
'-1',
'-2',
'0.1',
'0.2',
'1',
'2',
'10',
'11',
'12',
''
];
for (final (index, content) in numberCells.indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
);
}
// check the url cell
final urlCells = <String>[
'appflowy.io',
'no url',
'appflowy.io',
'https://github.com/AppFlowy-IO/',
'',
'',
];
for (final (index, content) in urlCells.indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.URL,
content: content,
);
}
// check the single select cell
final singleSelectCells = <String>[
's1',
's2',
's3',
's4',
's5',
'',
'',
'',
'',
'',
];
for (final (index, content) in singleSelectCells.indexed) {
await tester.assertSingleSelectOption(
rowIndex: index,
content: content,
);
}
// check the multi select cell
final List<List<String>> multiSelectCells = [
['m1'],
['m1', 'm2'],
['m1', 'm2', 'm3'],
['m1', 'm2', 'm3'],
['m1', 'm2', 'm3', 'm4', 'm5'],
[],
[],
[],
[],
[],
];
for (final (index, contents) in multiSelectCells.indexed) {
await tester.assertMultiSelectOption(
rowIndex: index,
contents: contents,
);
}
// check the checklist cell
final List<double> checklistCells = [
0.6,
0.3,
1.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
0.0,
];
for (final (index, percent) in checklistCells.indexed) {
await tester.assertChecklistCellInGrid(
rowIndex: index,
percent: percent,
);
}
// check the date cell
final List<String> dateCells = [
'Jun 01, 2023',
'Jun 02, 2023',
'Jun 03, 2023',
'Jun 04, 2023',
'Jun 05, 2023',
'Jun 05, 2023',
'Jun 16, 2023',
'',
'',
''
];
for (final (index, content) in dateCells.indexed) {
await tester.assertDateCellInGrid(
rowIndex: index,
fieldType: FieldType.DateTime,
content: content,
);
}
});
});
}
@@ -0,0 +1,268 @@
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
testWidgets('add text sort', (tester) async {
await tester.openV020database();
// create a sort
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.RichText, 'Name');
// check the text cell order
final textCells = <String>[
'A',
'B',
'C',
'D',
'E',
'',
'',
'',
'',
'',
];
for (final (index, content) in textCells.indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.RichText,
content: content,
);
}
// open the sort menu and select order by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('Name');
await tester.tapSortByDescending();
for (final (index, content) in <String>[
'E',
'D',
'C',
'B',
'A',
'',
'',
'',
'',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.RichText,
content: content,
);
}
// delete all sorts
await tester.tapSortMenuInSettingBar();
await tester.tapAllSortButton();
// check the text cell order
for (final (index, content) in <String>[
'A',
'B',
'C',
'D',
'E',
'',
'',
'',
'',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.RichText,
content: content,
);
}
await tester.pumpAndSettle();
});
testWidgets('add checkbox sort', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
// check the checkbox cell order
for (final (index, content) in <bool>[
false,
false,
false,
false,
false,
true,
true,
true,
true,
true,
].indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
// open the sort menu and select order by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('Done');
await tester.tapSortByDescending();
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
await tester.pumpAndSettle();
});
testWidgets('add number sort', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
// check the number cell order
for (final (index, content) in <String>[
'-2',
'-1',
'0.1',
'0.2',
'1',
'2',
'10',
'11',
'12',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
);
}
// open the sort menu and select order by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('number');
await tester.tapSortByDescending();
for (final (index, content) in <String>[
'12',
'11',
'10',
'2',
'1',
'0.2',
'0.1',
'-1',
'-2',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
);
}
await tester.pumpAndSettle();
});
testWidgets('add checkbox and number sort', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
// open the sort menu and sort checkbox by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('Done');
await tester.tapSortByDescending();
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
// add another sort, this time by number descending
await tester.tapSortMenuInSettingBar();
await tester.tapCreateSortByFieldTypeInSortMenu(
FieldType.Number,
'number',
);
await tester.tapSortButtonByName('number');
await tester.tapSortByDescending();
// check checkbox cell order
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCheckboxCell(
rowIndex: index,
isSelected: content,
);
}
// check number cell order
for (final (index, content) in <String>[
'1',
'0.2',
'0.1',
'-1',
'-2',
'12',
'11',
'10',
'2',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
);
}
await tester.pumpAndSettle();
});
});
}
@@ -0,0 +1,78 @@
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/database_test_op.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database', () {
testWidgets('create linked view', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
// Create grid view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.grid);
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Grid);
// Create calendar view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.calendar);
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Calendar);
await tester.pumpAndSettle();
});
testWidgets('rename and delete linked view', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
// rename board view
await tester.renameLinkedView(
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Board),
'new board',
);
final findBoard = tester.findTabBarLinkViewByViewName('new board');
expect(findBoard, findsOneWidget);
// delete the board
await tester.deleteDatebaseView(findBoard);
expect(tester.findTabBarLinkViewByViewName('new board'), findsNothing);
await tester.pumpAndSettle();
});
testWidgets('delete the last database view', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
tester.assertCurrentDatabaseTagIs(DatabaseLayoutPB.Board);
// delete the board
await tester.deleteDatebaseView(
tester.findTabBarLinkViewByViewLayout(ViewLayoutPB.Board),
);
await tester.pumpAndSettle();
});
});
}
@@ -4,27 +4,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
group('create and delete the document', () {
testWidgets('create a new document when launching app in first time',
(tester) async {
await tester.initializeAppFlowy();
@@ -32,9 +17,7 @@ void main() {
await tester.tapGoButton();
// create a new document
await tester.tapAddButton();
await tester.tapCreateDocumentButton();
await tester.pumpAndSettle();
await tester.createNewPageWithName();
// expect to see a new document
tester.expectToSeePageName(
@@ -50,19 +33,21 @@ void main() {
await tester.tapGoButton();
// delete the readme page
await tester.hoverOnPageName(readme);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
gettingStated,
onHover: () async => await tester.tapDeletePageButton(),
);
// the banner should show up and the readme page should be gone
tester.expectToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
// restore the readme page
await tester.tapRestoreButton();
// the banner should be gone and the readme page should be back
tester.expectNotToSeeDocumentBanner();
tester.expectToSeePageName(readme);
tester.expectToSeePageName(gettingStated);
});
testWidgets('delete the readme page and delete it permanently',
@@ -72,19 +57,21 @@ void main() {
await tester.tapGoButton();
// delete the readme page
await tester.hoverOnPageName(readme);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
gettingStated,
onHover: () async => await tester.tapDeletePageButton(),
);
// the banner should show up and the readme page should be gone
tester.expectToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
// delete the page permanently
await tester.tapDeletePermanentlyButton();
// the banner should be gone and the readme page should be gone
tester.expectNotToSeeDocumentBanner();
tester.expectNotToSeePageName(readme);
tester.expectNotToSeePageName(gettingStated);
});
});
}
@@ -0,0 +1,26 @@
import 'package:integration_test/integration_test.dart';
import 'document_create_and_delete_test.dart'
as document_create_and_delete_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_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_toggle_list_test.dart' as document_with_toggle_list_test;
import 'edit_document_test.dart' as document_edit_test;
import 'document_with_outline_block_test.dart' as document_with_outline_block;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Document integration tests
document_create_and_delete_test.main();
document_edit_test.main();
document_with_database_test.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();
}
@@ -0,0 +1,120 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/emoji.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('cover image', () {
testWidgets('document cover tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeNoDocumentCover();
// Hover over cover toolbar to show 'Add Cover' and 'Add Icon' buttons
await tester.editor.hoverOnCoverToolbar();
tester.expectToSeePluginAddCoverAndIconButton();
// 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 solid color background
await tester.editor.tapOnChangeCover();
await tester.editor.switchSolidColorBackground();
await tester.editor.dismissCoverPicker();
tester.expectToSeeDocumentCover(CoverType.color);
// Change cover to a network image
const imageUrl =
"https://raw.githubusercontent.com/AppFlowy-IO/AppFlowy/main/frontend/appflowy_flutter/assets/images/appflowy_launch_splash.jpg";
await tester.editor.hoverOnCover();
await tester.editor.tapOnChangeCover();
await tester.editor.addNetworkImageCover(imageUrl);
await tester.editor.switchNetworkImageCover(imageUrl);
await tester.editor.dismissCoverPicker();
tester.expectToSeeDocumentCover(CoverType.file);
// Remove the cover
await tester.editor.hoverOnCover();
await tester.editor.tapOnRemoveCover();
tester.expectToSeeNoDocumentCover();
});
testWidgets('document icon tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeDocumentIcon(null);
// Hover over cover toolbar to show the 'Add Cover' and 'Add Icon' buttons
await tester.editor.hoverOnCoverToolbar();
tester.expectToSeePluginAddCoverAndIconButton();
// Insert a document icon
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
tester.expectToSeeDocumentIcon('😀');
// Remove the document icon from the cover toolbar
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapRemoveIconButton();
tester.expectToSeeDocumentIcon(null);
// Add the icon back for further testing
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
tester.expectToSeeDocumentIcon('😀');
// Change the document icon
await tester.editor.tapOnIconWidget();
await tester.switchToEmojiList();
await tester.tapEmoji('😅');
tester.expectToSeeDocumentIcon('😅');
// Remove the document icon from the icon picker
await tester.editor.tapOnIconWidget();
await tester.editor.tapRemoveIconButton(isInPicker: true);
tester.expectToSeeDocumentIcon(null);
});
testWidgets('icon and cover at the same time', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeDocumentIcon(null);
tester.expectToSeeNoDocumentCover();
// Hover over cover toolbar to show the 'Add Cover' and 'Add Icon' buttons
await tester.editor.hoverOnCoverToolbar();
tester.expectToSeePluginAddCoverAndIconButton();
// Insert a document icon
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
// Insert a document cover
await tester.editor.tapOnAddCover();
// Expect to see the icon and cover at the same time
tester.expectToSeeDocumentIcon('😀');
tester.expectToSeeDocumentCover(CoverType.asset);
// Hover over the cover toolbar and see that neither icons are shown
await tester.editor.hoverOnCoverToolbar();
tester.expectToSeeEmptyDocumentHeaderToolbar();
});
});
}
@@ -0,0 +1,188 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.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 '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database view in document', () {
testWidgets('insert a referenced grid', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
});
testWidgets('insert a referenced board', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Board);
// validate the referenced board is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(BoardPage),
),
findsOneWidget,
);
});
testWidgets('insert a referenced calendar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Calendar);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(CalendarPage),
),
findsOneWidget,
);
});
testWidgets('create a grid inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
});
testWidgets('create a board inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Board);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(BoardPage),
),
findsOneWidget,
);
});
testWidgets('create a calendar inside a document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await createInlineDatabase(tester, ViewLayoutPB.Calendar);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(CalendarPage),
),
findsOneWidget,
);
});
});
}
/// Insert a referenced database of [layout] into the document
Future<void> insertReferenceDatabase(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new grid
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
name: 'insert_a_reference_${layout.name}',
layout: ViewLayoutPB.Document,
openAfterCreated: true,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a referenced view
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
layout.referencedMenuName,
);
final linkToPageMenu = find.byType(LinkToPageMenu);
expect(linkToPageMenu, findsOneWidget);
final referencedDatabase = find.descendant(
of: linkToPageMenu,
matching: find.findTextInFlowyText(name),
);
expect(referencedDatabase, findsOneWidget);
await tester.tapButton(referencedDatabase);
}
Future<void> createInlineDatabase(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new document
final documentName = 'insert_a_inline_${layout.name}';
await tester.createNewPageWithName(
name: documentName,
layout: ViewLayoutPB.Document,
openAfterCreated: true,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a referenced view
await tester.editor.showSlashMenu();
final name = switch (layout) {
ViewLayoutPB.Grid => LocaleKeys.document_slashMenu_grid_createANewGrid.tr(),
ViewLayoutPB.Board =>
LocaleKeys.document_slashMenu_board_createANewBoard.tr(),
ViewLayoutPB.Calendar =>
LocaleKeys.document_slashMenu_calendar_createANewCalendar.tr(),
_ => '',
};
await tester.editor.tapSlashMenuItemWithName(
name,
);
await tester.pumpAndSettle();
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(documentName))
.view
.childViews;
expect(childViews.length, 1);
}
@@ -0,0 +1,120 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.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_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
TestWidgetsFlutterBinding.ensureInitialized();
group('inline math equation in document', () {
testWidgets('insert an inline math equation', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// 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 inline math equation button
final inlineMathEquationButton = find.byTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
final inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsOneWidget);
// tap it and update the content
await tester.tapButton(inlineMathEquation);
final textFormField = find.descendant(
of: find.byType(MathInputTextField),
matching: find.byType(TextFormField),
);
const newFormula = 'E = MC ^ 3';
await tester.enterText(textFormField, newFormula);
await tester.tapButton(
find.descendant(
of: find.byType(MathInputTextField),
matching: find.byType(FlowyButton),
),
);
await tester.pumpAndSettle();
});
testWidgets('remove the inline math equation format', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// 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 inline math equation button
var inlineMathEquationButton = find.byTooltip(
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
var inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsOneWidget);
// highlight the math equation block
await tester.editor.updateSelection(
Selection.single(path: [0], startOffset: 0, endOffset: 1),
);
// expect to the see the inline math equation button is highlighted
inlineMathEquationButton = find.byWidgetPredicate(
(widget) =>
widget is SVGIconItemWidget &&
widget.tooltip ==
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
expect(
tester.widget<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
isTrue,
);
// cancel the format
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block is removed
inlineMathEquation = find.byType(InlineMathEquation);
expect(inlineMathEquation, findsNothing);
tester.expectToSeeText(formula);
});
});
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

@@ -0,0 +1,124 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('inline page view in document', () {
testWidgets('insert a inline page - grid', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Grid);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
await tester.tapButton(mentionBlock);
});
testWidgets('insert a inline page - board', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Board);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
await tester.tapButton(mentionBlock);
});
testWidgets('insert a inline page - calendar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Calendar);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
await tester.tapButton(mentionBlock);
});
testWidgets('insert a inline page - document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertingInlinePage(tester, ViewLayoutPB.Document);
final mentionBlock = find.byType(MentionPageBlock);
expect(mentionBlock, findsOneWidget);
await tester.tapButton(mentionBlock);
});
testWidgets('insert a inline page and rename it', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
// rename
const newName = 'RenameToNewPageName';
await tester.hoverOnPageName(
pageName,
onHover: () async => await tester.renamePage(newName),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(newName),
);
expect(finder, findsOneWidget);
});
testWidgets('insert a inline page and delete it', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
// rename
await tester.hoverOnPageName(
pageName,
layout: ViewLayoutPB.Grid,
onHover: () async => await tester.tapDeletePageButton(),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(pageName),
);
expect(finder, findsOneWidget);
await tester.tapButton(finder);
expect(find.byType(FlowyErrorPage), findsOneWidget);
});
});
}
/// Insert a referenced database of [layout] into the document
Future<String> insertingInlinePage(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new grid
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
name: 'insert_a_inline_page_${layout.name}',
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
await tester.editor.showAtMenu();
await tester.editor.tapAtMenuItemWithName(name);
return name;
}
@@ -0,0 +1,85 @@
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.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:url_launcher_platform_interface/url_launcher_platform_interface.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('test editing link in document', () {
late MockUrlLauncher mock;
setUp(() {
mock = MockUrlLauncher();
UrlLauncherPlatform.instance = mock;
});
testWidgets('insert/edit/open link', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a inline page
const link = 'AppFlowy';
await tester.ime.insertText(link);
await tester.editor.updateSelection(
Selection.single(path: [0], startOffset: 0, endOffset: link.length),
);
// tap the link button
final linkButton = find.byTooltip(
'Link',
);
await tester.tapButton(linkButton);
expect(find.text('Add your link', findRichText: true), findsOneWidget);
// input the link
const url = 'https://appflowy.io';
final textField = find.byWidgetPredicate(
(widget) => widget is TextField && widget.decoration!.hintText == 'URL',
);
await tester.enterText(textField, url);
await tester.testTextInput.receiveAction(TextInputAction.done);
await tester.pumpAndSettle();
// single-click the link menu to show the menu
await tester.tapButton(find.text(link, findRichText: true));
expect(find.text('Open link', findRichText: true), findsOneWidget);
expect(find.text('Copy link', findRichText: true), findsOneWidget);
expect(find.text('Remove link', findRichText: true), findsOneWidget);
// double-click the link menu to open the link
mock
..setLaunchExpectations(
url: url,
useSafariVC: false,
useWebView: false,
universalLinksOnly: false,
enableJavaScript: true,
enableDomStorage: true,
headers: <String, String>{},
webOnlyWindowName: null,
launchMode: PreferredLaunchMode.platformDefault,
)
..setResponse(true);
await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
await tester.doubleTapAt(
tester.getTopLeft(find.text(link, findRichText: true)).translate(5, 5),
);
expect(mock.canLaunchCalled, isTrue);
expect(mock.launchCalled, isTrue);
});
});
}
@@ -0,0 +1,102 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('outline block test', () {
testWidgets('insert an outline block', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
await insertOutlineInDocument(tester);
// validate the outline is inserted
expect(find.byType(OutlineBlockWidget), findsOneWidget);
});
testWidgets('insert an outline block and check if headings are visible',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('# Heading 1\n');
await tester.ime.insertText('## Heading 2\n');
await tester.ime.insertText('### Heading 3\n');
/* Results in:
* # Heading 1
* ## Heading 2
* ### Heading 3
*/
await tester.editor.tapLineOfEditorAt(3);
await insertOutlineInDocument(tester);
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text('Heading 1'),
),
findsOneWidget,
);
// Heading 2 is prefixed with a bullet
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text('Heading 2'),
),
findsOneWidget,
);
// Heading 3 is prefixed with a dash
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text('Heading 3'),
),
findsOneWidget,
);
// update the Heading 1 to Heading 1Hello world
await tester.editor.tapLineOfEditorAt(0);
await tester.ime.insertText('Hello world');
expect(
find.descendant(
of: find.byType(OutlineBlockWidget),
matching: find.text('Heading 1Hello world'),
),
findsOneWidget,
);
});
});
}
/// Inserts an outline block in the document
Future<void> insertOutlineInDocument(WidgetTester tester) async {
// open the actions menu and insert the outline block
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_selectionMenu_outline.tr(),
);
await tester.pumpAndSettle();
}
@@ -0,0 +1,227 @@
import 'dart:io';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.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 '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
TestWidgetsFlutterBinding.ensureInitialized();
group('toggle list in document', () {
void expectToggleListOpened() {
expect(find.byIcon(Icons.arrow_drop_down), findsOneWidget);
expect(find.byIcon(Icons.arrow_right), findsNothing);
}
void expectToggleListClosed() {
expect(find.byIcon(Icons.arrow_drop_down), findsNothing);
expect(find.byIcon(Icons.arrow_right), findsOneWidget);
}
testWidgets('convert > to toggle list, and click the icon to close it',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a toggle list
const text = 'This is a toggle list sample';
await tester.ime.insertText('> $text');
final editorState = tester.editor.getCurrentEditorState();
final toggleList = editorState.document.nodeAtPath([0])!;
expect(
toggleList.type,
ToggleListBlockKeys.type,
);
expect(
toggleList.attributes[ToggleListBlockKeys.collapsed],
false,
);
expect(
toggleList.delta!.toPlainText(),
text,
);
// Simulate pressing enter key to move the cursor to the next line
await tester.ime.insertCharacter('\n');
const text2 = 'This is a child node';
await tester.ime.insertText(text2);
expect(find.text(text2, findRichText: true), findsOneWidget);
// Click the toggle list icon to close it
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
await tester.tapButton(toggleListIcon);
// expect the toggle list to be closed
expect(find.text(text2, findRichText: true), findsNothing);
});
testWidgets('press enter key when the toggle list is closed',
(tester) async {
// if the toggle list is closed, press enter key will insert a new toggle list after it
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a toggle list
const text = 'Hello AppFlowy';
await tester.ime.insertText('> $text');
// Click the toggle list icon to close it
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
await tester.tapButton(toggleListIcon);
// Press the enter key
await tester.editor.updateSelection(
Selection.collapse(
[0],
'Hello '.length,
),
);
await tester.ime.insertCharacter('\n');
final editorState = tester.editor.getCurrentEditorState();
final node0 = editorState.getNodeAtPath([0])!;
final node1 = editorState.getNodeAtPath([1])!;
expect(node0.type, ToggleListBlockKeys.type);
expect(node0.attributes[ToggleListBlockKeys.collapsed], true);
expect(node0.delta!.toPlainText(), 'Hello ');
expect(node1.type, ToggleListBlockKeys.type);
expect(node1.delta!.toPlainText(), 'AppFlowy');
});
testWidgets('press enter key when the toggle list is open', (tester) async {
// if the toggle list is open, press enter key will insert a new paragraph inside it
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a toggle list
const text = 'Hello AppFlowy';
await tester.ime.insertText('> $text');
// Press the enter key
await tester.editor.updateSelection(
Selection.collapse(
[0],
'Hello '.length,
),
);
await tester.ime.insertCharacter('\n');
final editorState = tester.editor.getCurrentEditorState();
final node0 = editorState.getNodeAtPath([0])!;
final node00 = editorState.getNodeAtPath([0, 0])!;
final node1 = editorState.getNodeAtPath([1]);
expect(node0.type, ToggleListBlockKeys.type);
expect(node0.attributes[ToggleListBlockKeys.collapsed], false);
expect(node0.delta!.toPlainText(), 'Hello ');
expect(node00.type, ParagraphBlockKeys.type);
expect(node00.delta!.toPlainText(), 'AppFlowy');
expect(node1, isNull);
});
testWidgets('clear the format if toggle list if empty', (tester) async {
// if the toggle list is open, press enter key will insert a new paragraph inside it
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a toggle list
await tester.ime.insertText('> ');
// Press the enter key
// Click the toggle list icon to close it
final toggleListIcon = find.byIcon(Icons.arrow_drop_down);
await tester.tapButton(toggleListIcon);
await tester.editor.updateSelection(
Selection.collapse(
[0],
0,
),
);
await tester.ime.insertCharacter('\n');
final editorState = tester.editor.getCurrentEditorState();
final node0 = editorState.getNodeAtPath([0])!;
expect(node0.type, ParagraphBlockKeys.type);
});
testWidgets('use cmd/ctrl + enter to open/close the toggle list',
(tester) async {
// if the toggle list is open, press enter key will insert a new paragraph inside it
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document
await tester.createNewPageWithName(
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a toggle list
await tester.ime.insertText('> Hello');
expectToggleListOpened();
await tester.editor.updateSelection(
Selection.collapse(
[0],
0,
),
);
await tester.simulateKeyEvent(
LogicalKeyboardKey.enter,
isMetaPressed: Platform.isMacOS,
isControlPressed: Platform.isLinux || Platform.isWindows,
);
expectToggleListClosed();
await tester.simulateKeyEvent(
LogicalKeyboardKey.enter,
isMetaPressed: Platform.isMacOS,
isControlPressed: Platform.isLinux || Platform.isWindows,
);
expectToggleListOpened();
});
});
}
@@ -0,0 +1,134 @@
import 'dart:io';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('edit document', () {
testWidgets('redo & undo', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
// insert 1. to trigger it to be a numbered list
await tester.ime.insertText('1. ');
expect(find.text('1.', findRichText: true), findsOneWidget);
expect(
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
NumberedListBlockKeys.type,
);
// undo
// numbered list will be reverted to paragraph
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isWindows || Platform.isLinux,
isMetaPressed: Platform.isMacOS,
);
expect(
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
ParagraphBlockKeys.type,
);
// redo
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed: Platform.isWindows || Platform.isLinux,
isMetaPressed: Platform.isMacOS,
isShiftPressed: true,
);
expect(
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
NumberedListBlockKeys.type,
);
// switch to other page and switch back
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// the numbered list should be kept
expect(
tester.editor.getCurrentEditorState().getNodeAtPath([0])!.type,
NumberedListBlockKeys.type,
);
});
testWidgets('write a readme document', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
// mock inputting the sample
final lines = _sample.split('\n');
for (final line in lines) {
await tester.ime.insertText(line);
await tester.ime.insertCharacter('\n');
}
// switch to other page and switch back
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// this screenshots are different on different platform, so comment it out temporarily.
// check the document
// await expectLater(
// find.byType(AppFlowyEditor),
// matchesGoldenFile('document/edit_document_test.png'),
// );
});
});
}
const _sample = r'''
# Heading 1
## Heading 2
### Heading 3
---
[] Highlight any text, and use the editing menu to _style_ **your** writing `however` you ~~like.~~
[] Type followed by bullet or num to create a list.
[x] Click `+ New Page` button at the bottom of your sidebar to add a new page.
[] Click `+` next to any page title in the sidebar to quickly add a new subpage, `Document`, `Grid`, or `Kanban Board`.
---
* bulleted list 1
* bulleted list 2
* bulleted list 3
bulleted list 4
---
1. numbered list 1
2. numbered list 2
3. numbered list 3
numbered list 4
---
" quote''';
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

@@ -1,110 +0,0 @@
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('database view in document', () {
const location = 'database_view';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('insert a referenced grid', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Grid);
// validate the referenced grid is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(GridPage),
),
findsOneWidget,
);
});
testWidgets('insert a referenced board', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await insertReferenceDatabase(tester, ViewLayoutPB.Board);
// validate the referenced board is inserted
expect(
find.descendant(
of: find.byType(AppFlowyEditor),
matching: find.byType(BoardPage),
),
findsOneWidget,
);
});
// testWidgets('insert a referenced calendar', (tester) async {
// await tester.initializeAppFlowy();
// await tester.tapGoButton();
// await insertReferenceDatabase(tester, ViewLayoutPB.Calendar);
// // validate the referenced grid is inserted
// expect(
// find.descendant(
// of: find.byType(AppFlowyEditor),
// matching: find.byType(CalendarPage),
// ),
// findsOneWidget,
// );
// });
});
}
/// Insert a referenced database of [layout] into the document
Future<void> insertReferenceDatabase(
WidgetTester tester,
ViewLayoutPB layout,
) async {
// create a new grid
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_reference_${layout.name}',
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
// insert a referenced grid
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
layout.referencedMenuName,
);
final linkToPageMenu = find.byType(LinkToPageMenu);
expect(linkToPageMenu, findsOneWidget);
final referencedDatabase = find.descendant(
of: linkToPageMenu,
matching: find.findTextInFlowyText(name),
);
expect(referencedDatabase, findsOneWidget);
await tester.tapButton(referencedDatabase);
}
@@ -0,0 +1,95 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.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 'util/keyboard.dart';
import 'util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('hotkeys test', () {
testWidgets('toggle theme mode', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeHomePage();
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.appearance);
await tester.pumpAndSettle();
tester.expectToSeeText(
LocaleKeys.settings_appearance_themeMode_system.tr(),
);
await tester.tapButton(
find.bySemanticsLabel(
LocaleKeys.settings_appearance_themeMode_system.tr(),
),
);
await tester.pumpAndSettle();
await tester.tapButton(
find.bySemanticsLabel(
LocaleKeys.settings_appearance_themeMode_dark.tr(),
),
);
await tester.pumpAndSettle(const Duration(seconds: 1));
await tester.tap(find.byType(SettingsDialog));
await tester.pumpAndSettle();
await FlowyTestKeyboard.simulateKeyDownEvent(
[
Platform.isMacOS
? LogicalKeyboardKey.meta
: LogicalKeyboardKey.control,
LogicalKeyboardKey.shift,
LogicalKeyboardKey.keyL,
],
tester: tester,
);
await tester.pumpAndSettle();
tester.expectToSeeText(
LocaleKeys.settings_appearance_themeMode_light.tr(),
);
});
testWidgets('show or hide home menu', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
tester.expectToSeeHomePage();
await tester.pumpAndSettle();
expect(find.byType(HomeMenu), findsOneWidget);
await FlowyTestKeyboard.simulateKeyDownEvent(
[
Platform.isMacOS
? LogicalKeyboardKey.meta
: LogicalKeyboardKey.control,
LogicalKeyboardKey.backslash,
],
tester: tester,
);
await tester.pumpAndSettle();
expect(find.byType(HomeMenu), findsNothing);
});
});
}
@@ -12,44 +12,32 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('import files', () {
const location = 'import_files';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('import multiple markdown files', (tester) async {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
// expect to see a getting started page
tester.expectToSeePageName(gettingStated);
await tester.tapAddButton();
await tester.tapAddViewButton();
await tester.tapImportButton();
final testFileNames = ['test1.md', 'test2.md'];
final fileLocation = await tester.currentFileLocation();
final paths = <String>[];
for (final fileName in testFileNames) {
final str = await rootBundle.loadString(
p.join(
'assets/test/workspaces/markdowns',
fileName,
),
'assets/test/workspaces/markdowns/$fileName',
);
File(p.join(fileLocation, fileName)).writeAsStringSync(str);
final path = p.join(context.applicationDataDirectory, fileName);
paths.add(path);
File(path).writeAsStringSync(str);
}
// mock get files
await mockPickFilePaths(testFileNames, name: location);
await mockPickFilePaths(
paths: testFileNames
.map((e) => p.join(context.applicationDataDirectory, e))
.toList(),
);
await tester.tapTextAndMarkdownButton();
@@ -8,16 +8,6 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('document', () {
const location = 'appflowy';
setUpAll(() async {
await TestFolder.setTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets(
'change the language successfully when launching the app for the first time',
(tester) async {
@@ -17,7 +17,8 @@ void main() {
setUpAll(() async => await service.setUpAll());
setUp(() async => await service.setUp());
testWidgets('testing selection on open-ai smart menu replace', (tester) async {
testWidgets('testing selection on open-ai smart menu replace',
(tester) async {
final appFlowyEditor = await setUpOpenAITesting(tester);
final editorState = appFlowyEditor.editorState;
@@ -38,7 +39,8 @@ void main() {
await tester.tap(find.text('Summarize'));
await tester.pumpAndSettle();
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
await tester
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
await tester.pumpAndSettle();
expect(
@@ -49,7 +51,8 @@ void main() {
),
);
});
testWidgets('testing selection on open-ai smart menu insert', (tester) async {
testWidgets('testing selection on open-ai smart menu insert',
(tester) async {
final appFlowyEditor = await setUpOpenAITesting(tester);
final editorState = appFlowyEditor.editorState;
@@ -69,7 +72,8 @@ void main() {
await tester.tap(find.text('Summarize'));
await tester.pumpAndSettle();
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
await tester
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
await tester.pumpAndSettle();
expect(
@@ -1,11 +1,21 @@
import 'package:integration_test/integration_test.dart';
import 'switch_folder_test.dart' as switch_folder_test;
import 'document_test.dart' as document_test;
import 'cover_image_test.dart' as cover_image_test;
import 'share_markdown_test.dart' as share_markdown_test;
import 'database_calendar_test.dart' as database_calendar_test;
import 'database_cell_test.dart' as database_cell_test;
import 'database_field_test.dart' as database_field_test;
import 'database_filter_test.dart' as database_filter_test;
import 'database_row_page_test.dart' as database_row_page_test;
import 'database_row_test.dart' as database_row_test;
import 'database_setting_test.dart' as database_setting_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;
import 'document/document_test_runner.dart' as document_test_runner;
import 'import_files_test.dart' as import_files_test;
import 'document_with_database_test.dart' as document_with_database_test;
import 'share_markdown_test.dart' as share_markdown_test;
import 'switch_folder_test.dart' as switch_folder_test;
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
import 'board/board_test_runner.dart' as board_test_runner;
/// The main task runner for all integration tests in AppFlowy.
///
@@ -17,11 +27,30 @@ import 'document_with_database_test.dart' as document_with_database_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
switch_folder_test.main();
cover_image_test.main();
document_test.main();
share_markdown_test.main();
import_files_test.main();
document_with_database_test.main();
// Document integration tests
document_test_runner.startTesting();
// Sidebar integration tests
sidebar_test_runner.startTesting();
// Board integration test
board_test_runner.startTesting();
// Database integration tests
database_cell_test.main();
database_field_test.main();
database_share_test.main();
database_row_page_test.main();
database_row_test.main();
database_setting_test.main();
database_filter_test.main();
database_sort_test.main();
database_view_test.main();
database_calendar_test.main();
// board_test.main();
// empty_document_test.main();
// smart_menu_test.main();
@@ -3,7 +3,7 @@ import 'dart:io';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'util/mock/mock_file_picker.dart';
import 'util/util.dart';
@@ -11,30 +11,17 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('share markdown in document page', () {
const location = 'markdown';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('click the share button in document page', (tester) async {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
tester.expectToSeePageName(gettingStated);
// mock the file picker
final path = await mockSaveFilePath(location, 'test.md');
final path = await mockSaveFilePath(
p.join(context.applicationDataDirectory, 'test.md'),
);
// click the share button and select markdown
await tester.tapShareButton();
await tester.tapMarkdownButton();
@@ -52,21 +39,29 @@ void main() {
testWidgets(
'share the markdown after renaming the document name',
(tester) async {
await tester.initializeAppFlowy();
final context = await tester.initializeAppFlowy();
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
// expect to see a getting started page
tester.expectToSeePageName(gettingStated);
// rename the document
await tester.hoverOnPageName(readme);
await tester.renamePage('example');
await tester.hoverOnPageName(
gettingStated,
onHover: () async {
await tester.renamePage('example');
},
);
final shareButton = find.byType(ShareActionList);
final shareButtonState =
tester.state(shareButton) as ShareActionListState;
final path =
await mockSaveFilePath(location, '${shareButtonState.name}.md');
final path = await mockSaveFilePath(
p.join(
context.applicationDataDirectory,
'${shareButtonState.name}.md',
),
);
// click the share button and select markdown
await tester.tapShareButton();
@@ -0,0 +1,48 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/personal_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar expand test', () {
bool isExpanded({required FolderCategoryType type}) {
if (type == FolderCategoryType.personal) {
return find
.descendant(
of: find.byType(PersonalFolder),
matching: find.byType(ViewItem),
)
.evaluate()
.isNotEmpty;
}
return false;
}
testWidgets('first time the personal folder is expanded', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// first time is expanded
expect(isExpanded(type: FolderCategoryType.personal), true);
// collapse the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
);
expect(isExpanded(type: FolderCategoryType.personal), false);
// expand the personal folder
await tester.tapButton(
find.byTooltip(LocaleKeys.sideBar_clickToHidePersonal.tr()),
);
expect(isExpanded(type: FolderCategoryType.personal), true);
});
});
}
@@ -0,0 +1,175 @@
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/favorite_folder.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/base.dart';
import '../util/common_operations.dart';
import '../util/expectation.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Favorites', () {
testWidgets(
'Toggle favorites for views creates / removes the favorite header along with favorite views',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// no favorite folder
expect(find.byType(FavoriteFolder), findsNothing);
// create the nested views
final names = [
1,
2,
].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(
names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
);
}
await tester.favoriteViewByName(gettingStated);
expect(
tester.findFavoritePageName(gettingStated),
findsOneWidget,
);
await tester.favoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(names[1]),
findsNWidgets(2),
);
await tester.unfavoriteViewByName(gettingStated);
expect(
tester.findFavoritePageName(gettingStated),
findsNothing,
);
expect(
tester.findFavoritePageName(
names[1],
),
findsOneWidget,
);
await tester.unfavoriteViewByName(names[1]);
expect(
tester.findFavoritePageName(
names[1],
),
findsNothing,
);
});
testWidgets(
'renaming a favorite view updates name under favorite header',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const name = 'test';
await tester.favoriteViewByName(gettingStated);
await tester.hoverOnPageName(
gettingStated,
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.renamePage(name);
await tester.pumpAndSettle();
},
);
expect(
tester.findPageName(name),
findsNWidgets(2),
);
expect(
tester.findFavoritePageName(name),
findsNothing,
);
},
);
testWidgets(
'deleting first level favorite view removes its instance from favorite header, deleting root level views leads to removal of all favorites that are its children',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final names = [1, 2].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(names[i], parentName: parentName);
}
await tester.favoriteViewByName(gettingStated);
await tester.favoriteViewByName(names[0]);
await tester.favoriteViewByName(names[1]);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNWidgets(6),
);
await tester.hoverOnPageName(
names[1],
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.tapDeletePageButton();
await tester.pumpAndSettle();
},
);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNWidgets(3),
);
await tester.hoverOnPageName(
gettingStated,
layout: ViewLayoutPB.Document,
onHover: () async {
await tester.tapDeletePageButton();
await tester.pumpAndSettle();
},
);
expect(
find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite,
),
findsNothing,
);
},
);
});
}
@@ -0,0 +1,209 @@
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('sidebar test', () {
testWidgets('create a new page', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
// create a new page
const name = 'Hello AppFlowy';
await tester.tapNewPageButton();
await tester.enterText(find.byType(TextFormField), name);
await tester.tapOKButton();
// expect to see a new document
tester.expectToSeePageName(
name,
);
// and with one paragraph block
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
});
testWidgets('create a new document, grid, board and calendar',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
for (final layout in ViewLayoutPB.values) {
// create a new page
final name = 'AppFlowy_$layout';
await tester.createNewPageWithName(
name: name,
layout: layout,
);
// expect to see a new page
tester.expectToSeePageName(
name,
layout: layout,
);
switch (layout) {
case ViewLayoutPB.Document:
// and with one paragraph block
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
break;
case ViewLayoutPB.Grid:
expect(find.byType(GridPage), findsOneWidget);
break;
case ViewLayoutPB.Board:
expect(find.byType(BoardPage), findsOneWidget);
break;
case ViewLayoutPB.Calendar:
expect(find.byType(CalendarPage), findsOneWidget);
break;
}
await tester.openPage(gettingStated);
}
});
testWidgets('create some nested pages, and move them', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
for (var i = 0; i < names.length; i++) {
final parentName = i == 0 ? gettingStated : names[i - 1];
await tester.createNewPageWithName(
name: names[i],
parentName: parentName,
layout: ViewLayoutPB.Document,
);
tester.expectToSeePageName(names[i], parentName: parentName);
}
// move the document_3 to the getting started page
await tester.movePageToOtherPage(
name: names[3],
parentName: gettingStated,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
);
final fromId = tester
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
.view
.parentViewId;
final toId = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.id;
expect(fromId, toId);
// move the document_2 before document_1
await tester.movePageToOtherPage(
name: names[2],
parentName: gettingStated,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Document,
position: DraggableHoverPosition.bottom,
);
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.childViews;
expect(
childViews[0].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[2]))
.view
.id,
);
expect(
childViews[1].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[0]))
.view
.id,
);
expect(
childViews[2].id,
tester
.widget<SingleInnerViewItem>(tester.findPageName(names[3]))
.view
.id,
);
});
testWidgets('unable to move a document into a database', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const document = 'document';
await tester.createNewPageWithName(
name: document,
openAfterCreated: false,
);
tester.expectToSeePageName(document, layout: ViewLayoutPB.Document);
const grid = 'grid';
await tester.createNewPageWithName(
name: grid,
layout: ViewLayoutPB.Grid,
openAfterCreated: false,
);
tester.expectToSeePageName(grid, layout: ViewLayoutPB.Grid);
// move the document to the grid page
await tester.movePageToOtherPage(
name: document,
parentName: grid,
layout: ViewLayoutPB.Document,
parentLayout: ViewLayoutPB.Grid,
);
// it should not be moved
final childViews = tester
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
.view
.childViews;
expect(
childViews[0].name,
document,
);
expect(
childViews[1].name,
grid,
);
});
testWidgets('unable to create a new database inside the existing one',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
const grid = 'grid';
await tester.createNewPageWithName(
name: grid,
layout: ViewLayoutPB.Grid,
openAfterCreated: true,
);
tester.expectToSeePageName(grid, layout: ViewLayoutPB.Grid);
await tester.hoverOnPageName(
grid,
layout: ViewLayoutPB.Grid,
onHover: () async {
expect(find.byType(ViewAddButton), findsNothing);
expect(find.byType(ViewMoreActionButton), findsOneWidget);
},
);
});
});
}
@@ -0,0 +1,14 @@
import 'package:integration_test/integration_test.dart';
import 'sidebar_test.dart' as sidebar_test;
import 'sidebar_expand_test.dart' as sidebar_expanded_test;
import 'sidebar_favorites_test.dart' as sidebar_favorite_test;
void startTesting() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
// Sidebar integration tests
sidebar_test.main();
sidebar_expanded_test.main();
sidebar_favorite_test.main();
}
@@ -1,7 +1,9 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/prelude.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'util/mock/mock_file_picker.dart';
import 'util/util.dart';
@@ -9,37 +11,26 @@ void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('customize the folder path', () {
const location = 'appflowy';
setUp(() async {
await TestFolder.cleanTestLocation(location);
await TestFolder.setTestLocation(location);
});
tearDown(() async {
await TestFolder.cleanTestLocation(location);
});
tearDownAll(() async {
await TestFolder.cleanTestLocation(null);
});
testWidgets('switch to B from A, then switch to A again', (tester) async {
const String userA = 'userA';
const String userB = 'userB';
const userA = 'UserA';
const userB = 'UserB';
await TestFolder.cleanTestLocation(userA);
await TestFolder.cleanTestLocation(userB);
await TestFolder.setTestLocation(userA);
await tester.initializeAppFlowy();
final initialPath = p.join(userA, appFlowyDataFolder);
final context = await tester.initializeAppFlowy(
pathExtension: initialPath,
);
// remove the last extension
final rootPath = context.applicationDataDirectory.replaceFirst(
initialPath,
'',
);
await tester.tapGoButton();
tester.expectToSeeHomePage();
// switch to user B
{
// set user name to userA
// set user name for userA
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userA);
@@ -48,12 +39,14 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userB);
await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
tester.expectToSeeHomePage();
// set user name to userB
// set user name for userB
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.user);
await tester.enterUserName(userB);
@@ -65,7 +58,9 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userA);
await mockGetDirectoryPath(
p.join(rootPath, userA),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
@@ -80,16 +75,15 @@ void main() {
await tester.pumpAndSettle();
// mock the file_picker result
await mockGetDirectoryPath(userB);
await mockGetDirectoryPath(
p.join(rootPath, userB),
);
await tester.tapCustomLocationButton();
await tester.pumpAndSettle();
tester.expectToSeeHomePage();
tester.expectToSeeUserName(userB);
}
await TestFolder.cleanTestLocation(userA);
await TestFolder.cleanTestLocation(userB);
});
testWidgets('reset to default location', (tester) async {
@@ -106,8 +100,8 @@ void main() {
await tester.restoreLocation();
expect(
await TestFolder.defaultDevelopmentLocation(),
await TestFolder.currentLocation(),
await appFlowyApplicationDataDirectory().then((value) => value.path),
await getIt<ApplicationDataStorage>().getPath(),
);
});
});
@@ -0,0 +1,75 @@
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-folder2/view.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'util/base.dart';
import 'util/common_operations.dart';
const _readmeName = 'Read me';
const _documentName = 'Document';
const _calendarName = 'Calendar';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('Tabs', () {
testWidgets('Open AppFlowy and open/navigate multiple tabs',
(tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
expect(
find.descendant(
of: find.byType(TabsManager),
matching: find.byType(TabBar),
),
findsNothing,
);
await tester.createNewPageWithName(
name: _calendarName,
layout: ViewLayoutPB.Calendar,
);
await tester.createNewPageWithName(
name: _documentName,
layout: ViewLayoutPB.Document,
);
// Navigate current view to "Read me" document again
await tester.tapButtonWithName(_readmeName);
/// Open second menu item in a new tab
await tester.openAppInNewTab(_calendarName);
/// Open third menu item in a new tab
await tester.openAppInNewTab(_documentName);
expect(
find.descendant(
of: find.byType(TabsManager),
matching: find.byType(TabBar),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byType(TabBar),
matching: find.byType(FlowyTab),
),
findsNWidgets(3),
);
/// Navigate to the first tab
await tester.tap(
find.descendant(
of: find.byType(FlowyTab),
matching: find.text(_readmeName),
),
);
});
});
}
@@ -1,91 +1,96 @@
import 'dart:io';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/main.dart' as app;
import 'package:appflowy/startup/tasks/prelude.dart';
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:path/path.dart' as p;
class TestFolder {
/// Location / Path
class FlowyTestContext {
FlowyTestContext({
required this.applicationDataDirectory,
});
/// Set a given AppFlowy data storage location under test environment.
///
/// To pass null means clear the location.
///
/// The file_picker is a system component and can't be tapped, so using logic instead of tapping.
///
static Future<void> setTestLocation(String? name) async {
final location = await testLocation(name);
SharedPreferences.setMockInitialValues({
KVKeys.pathLocation: location.path,
});
return;
}
/// Clean the location.
static Future<void> cleanTestLocation(String? name) async {
final dir = await testLocation(name);
await dir.delete(recursive: true);
return;
}
/// Get current using location.
static Future<String> currentLocation() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString(KVKeys.pathLocation)!;
}
/// Get default location under development environment.
static Future<String> defaultDevelopmentLocation() async {
final dir = await appFlowyDocumentDirectory();
return dir.path;
}
/// Get default location under test environment.
static Future<Directory> testLocation(String? name) async {
final dir = await getApplicationDocumentsDirectory();
var path = '${dir.path}/flowy_test';
if (name != null) {
path += '/$name';
}
return Directory(path).create(recursive: true);
}
final String applicationDataDirectory;
}
extension AppFlowyTestBase on WidgetTester {
Future<void> initializeAppFlowy() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(
const MethodChannel('hotkey_manager'),
(MethodCall methodCall) async {
if (methodCall.method == 'unregisterAll') {
// do nothing
}
Future<FlowyTestContext> initializeAppFlowy({
// use to append after the application data directory
String? pathExtension,
Size windowsSize = const Size(1600, 1200),
}) async {
binding.setSurfaceSize(windowsSize);
return;
},
mockHotKeyManagerHandlers();
final directory = await mockApplicationDataStorage(
pathExtension: pathExtension,
);
WidgetsFlutterBinding.ensureInitialized();
await FlowyRunner.run(
FlowyApp(),
IntegrationMode.integrationTest,
);
await app.main();
await wait(3000);
await pumpAndSettle(const Duration(seconds: 2));
return FlowyTestContext(
applicationDataDirectory: directory,
);
}
void mockHotKeyManagerHandlers() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(const MethodChannel('hotkey_manager'),
(MethodCall methodCall) async {
if (methodCall.method == 'unregisterAll') {
// do nothing
}
return;
});
}
Future<String> mockApplicationDataStorage({
// use to append after the application data directory
String? pathExtension,
}) async {
final dir = await getTemporaryDirectory();
// Use a random uuid to avoid conflict.
String path = p.join(dir.path, 'appflowy_integration_test', uuid());
if (pathExtension != null && pathExtension.isNotEmpty) {
path = '$path/$pathExtension';
}
final directory = Directory(path);
if (!directory.existsSync()) {
await directory.create(recursive: true);
}
MockApplicationDataStorage.initialPath = directory.path;
return directory.path;
}
Future<void> tapButton(
Finder finder, {
int? pointer,
int buttons = kPrimaryButton,
bool warnIfMissed = true,
bool warnIfMissed = false,
int milliseconds = 500,
}) async {
await tap(finder);
await tap(
finder,
buttons: buttons,
warnIfMissed: warnIfMissed,
);
await pumpAndSettle(Duration(milliseconds: milliseconds));
return;
}
Future<void> tapButtonWithName(
@@ -99,7 +104,7 @@ extension AppFlowyTestBase on WidgetTester {
);
if (button.evaluate().isEmpty) {
button = find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == tr,
(widget) => widget is FlowyText && widget.text == tr,
);
}
await tapButton(
@@ -121,6 +126,43 @@ extension AppFlowyTestBase on WidgetTester {
return;
}
Future<void> doubleTapAt(
Offset location, {
int? pointer,
int buttons = kPrimaryButton,
int milliseconds = 500,
}) async {
await tapAt(location, pointer: pointer, buttons: buttons);
await pump(kDoubleTapMinTime);
await tapAt(location, pointer: pointer, buttons: buttons);
await pumpAndSettle(Duration(milliseconds: milliseconds));
}
Future<void> doubleTapButton(
Finder finder, {
int? pointer,
int buttons = kPrimaryButton,
bool warnIfMissed = true,
int milliseconds = 500,
}) async {
await tap(
finder,
pointer: pointer,
buttons: buttons,
warnIfMissed: warnIfMissed,
);
await pump(kDoubleTapMinTime);
await tap(
finder,
buttons: buttons,
pointer: pointer,
warnIfMissed: warnIfMissed,
);
await pumpAndSettle(Duration(milliseconds: milliseconds));
}
Future<void> wait(int milliseconds) async {
await pumpAndSettle(Duration(milliseconds: milliseconds));
return;
@@ -130,7 +172,7 @@ extension AppFlowyTestBase on WidgetTester {
extension AppFlowyFinderTestBase on CommonFinders {
Finder findTextInFlowyText(String text) {
return find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == text,
(widget) => widget is FlowyText && widget.text == text,
);
}
}
@@ -1,26 +1,26 @@
import 'dart:ui';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/header/add_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'util.dart';
extension CommonOperations on WidgetTester {
/// Get current file location of AppFlowy.
Future<String> currentFileLocation() async {
return TestFolder.currentLocation();
}
/// Tap the GetStart button on the launch page.
Future<void> tapGoButton() async {
final goButton = find.byType(GoButton);
@@ -28,28 +28,48 @@ extension CommonOperations on WidgetTester {
}
/// Tap the + button on the home page.
Future<void> tapAddButton() async {
final addButton = find.byType(AddButton);
await tapButton(addButton);
Future<void> tapAddViewButton({
String name = gettingStated,
}) async {
await hoverOnPageName(
name,
onHover: () async {
final addButton = find.byType(ViewAddButton);
await tapButton(addButton);
},
);
}
/// Tap the 'New Page' Button on the sidebar.
Future<void> tapNewPageButton() async {
final newPageButton = find.byType(SidebarNewPageButton);
await tapButton(newPageButton);
}
/// Tap the create document button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapCreateDocumentButton() async {
await tapButtonWithName(LocaleKeys.document_menuName.tr());
}
/// Tap the create grid button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapCreateGridButton() async {
await tapButtonWithName(LocaleKeys.grid_menuName.tr());
}
/// Tap the create grid button.
///
/// Must call [tapAddViewButton] first.
Future<void> tapCreateCalendarButton() async {
await tapButtonWithName(LocaleKeys.calendar_menuName.tr());
}
/// Tap the import button.
///
/// Must call [tapAddButton] first.
/// Must call [tapAddViewButton] first.
Future<void> tapImportButton() async {
await tapButtonWithName(LocaleKeys.moreAction_import.tr());
}
@@ -113,6 +133,7 @@ extension CommonOperations on WidgetTester {
Finder finder, {
Offset? offset,
Future<void> Function()? onHover,
bool removePointer = true,
}) async {
try {
final gesture = await createGesture(kind: PointerDeviceKind.mouse);
@@ -130,34 +151,64 @@ extension CommonOperations on WidgetTester {
/// Hover on the page name.
Future<void> hoverOnPageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
Future<void> Function()? onHover,
bool useLast = true,
}) async {
final pageNames = findPageName(name, layout: layout);
if (useLast) {
await hoverOnWidget(findPageName(name).last, onHover: onHover);
await hoverOnWidget(
pageNames.last,
onHover: onHover,
);
} else {
await hoverOnWidget(findPageName(name).first, onHover: onHover);
await hoverOnWidget(
pageNames.first,
onHover: onHover,
);
}
}
/// open the page with given name.
Future<void> openPage(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
final finder = findPageName(name, layout: layout);
expect(finder, findsOneWidget);
await tapButton(finder);
}
/// Tap the ... button beside the page name.
///
/// Must call [hoverOnPageName] first.
Future<void> tapPageOptionButton() async {
final optionButton = find.byType(ViewDisclosureButton);
final optionButton = find.byType(ViewMoreActionButton);
await tapButton(optionButton);
}
/// Tap the delete page button.
Future<void> tapDeletePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.delete.name);
await tapButtonWithName(ViewMoreActionType.delete.name);
}
/// Tap the rename page button.
Future<void> tapRenamePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewDisclosureAction.rename.name);
await tapButtonWithName(ViewMoreActionType.rename.name);
}
/// Tap the favorite page button
Future<void> tapFavoritePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewMoreActionType.favorite.name);
}
/// Tap the unfavorite page button
Future<void> tapUnfavoritePageButton() async {
await tapPageOptionButton();
await tapButtonWithName(ViewMoreActionType.unFavorite.name);
}
/// Rename the page.
@@ -214,20 +265,140 @@ extension CommonOperations on WidgetTester {
await tapButton(markdownButton);
}
Future<void> createNewPageWithName(ViewLayoutPB layout, String name) async {
Future<void> createNewPageWithName({
String? name,
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
bool openAfterCreated = true,
}) async {
// create a new page
await tapAddButton();
await tapAddViewButton(name: parentName ?? gettingStated);
await tapButtonWithName(layout.menuName);
await pumpAndSettle();
// hover on it and change it's name
if (name != null) {
await hoverOnPageName(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: layout,
onHover: () async {
await renamePage(name);
await pumpAndSettle();
},
);
await pumpAndSettle();
}
// open the page after created
if (openAfterCreated) {
await openPage(
// if the name is null, use the default name
name ?? LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
layout: layout,
);
await pumpAndSettle();
}
}
Future<void> simulateKeyEvent(
LogicalKeyboardKey key, {
bool isControlPressed = false,
bool isShiftPressed = false,
bool isAltPressed = false,
bool isMetaPressed = false,
}) async {
if (isControlPressed) {
await simulateKeyDownEvent(LogicalKeyboardKey.control);
}
if (isShiftPressed) {
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
}
if (isAltPressed) {
await simulateKeyDownEvent(LogicalKeyboardKey.alt);
}
if (isMetaPressed) {
await simulateKeyDownEvent(LogicalKeyboardKey.meta);
}
await simulateKeyDownEvent(key);
await simulateKeyUpEvent(key);
if (isControlPressed) {
await simulateKeyUpEvent(LogicalKeyboardKey.control);
}
if (isShiftPressed) {
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
}
if (isAltPressed) {
await simulateKeyUpEvent(LogicalKeyboardKey.alt);
}
if (isMetaPressed) {
await simulateKeyUpEvent(LogicalKeyboardKey.meta);
}
await pumpAndSettle();
}
Future<void> openAppInNewTab(String name) async {
await hoverOnPageName(name);
await tap(find.byType(ViewDisclosureButton));
await pumpAndSettle();
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
await pumpAndSettle();
}
Future<void> favoriteViewByName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
name,
layout: layout,
useLast: true,
onHover: () async {
await renamePage(name);
await tapFavoritePageButton();
await pumpAndSettle();
},
);
}
Future<void> unfavoriteViewByName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
await hoverOnPageName(
name,
layout: layout,
useLast: true,
onHover: () async {
await tapUnfavoritePageButton();
await pumpAndSettle();
},
);
}
Future<void> movePageToOtherPage({
required String name,
required String parentName,
required ViewLayoutPB layout,
required ViewLayoutPB parentLayout,
DraggableHoverPosition position = DraggableHoverPosition.center,
}) async {
final from = findPageName(name, layout: layout);
final to = findPageName(parentName, layout: parentLayout);
final gesture = await startGesture(getCenter(from));
Offset offset = Offset.zero;
switch (position) {
case DraggableHoverPosition.center:
offset = getCenter(to);
break;
case DraggableHoverPosition.top:
offset = getTopLeft(to);
break;
case DraggableHoverPosition.bottom:
offset = getBottomLeft(to);
break;
default:
}
await gesture.moveTo(offset, timeStamp: const Duration(milliseconds: 400));
await gesture.up();
await pumpAndSettle();
}
}
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,17 @@
import 'dart:ui';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_popover.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
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 'ime.dart';
import 'util.dart';
extension EditorWidgetTester on WidgetTester {
@@ -13,21 +23,117 @@ class EditorOperations {
final WidgetTester tester;
EditorState getCurrentEditorState() {
return tester
.widget<AppFlowyEditor>(find.byType(AppFlowyEditor))
.editorState;
}
/// Tap the line of editor at [index]
Future<void> tapLineOfEditorAt(int index) async {
final textBlocks = find.byType(TextBlockComponentWidget);
final textBlocks = find.byType(AppFlowyRichText);
index = index.clamp(0, textBlocks.evaluate().length - 1);
await tester.tapAt(tester.getTopRight(textBlocks.at(index)));
await tester.pumpAndSettle();
}
/// Hover on cover plugin button above the document
Future<void> hoverOnCoverPluginAddButton() async {
final editor = find.byWidgetPredicate(
(widget) => widget is AppFlowyEditor,
Future<void> hoverOnCoverToolbar() async {
final coverToolbar = find.byType(DocumentHeaderToolbar);
await tester.startGesture(
tester.getBottomLeft(coverToolbar).translate(5, -5),
kind: PointerDeviceKind.mouse,
);
await tester.hoverOnWidget(
editor,
offset: tester.getTopLeft(editor).translate(20, 20),
await tester.pumpAndSettle();
}
/// Taps on the 'Add Icon' button in the cover toolbar
Future<void> tapAddIconButton() async {
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_addIcon.tr(),
);
expect(find.byType(EmojiPopover), findsOneWidget);
}
/// Taps the 'Remove Icon' button in the cover toolbar and the icon popover
Future<void> tapRemoveIconButton({bool isInPicker = false}) async {
Finder button =
find.text(LocaleKeys.document_plugins_cover_removeIcon.tr());
if (isInPicker) {
button = find.descendant(of: find.byType(EmojiPopover), matching: button);
}
await tester.tapButton(button);
}
/// Requires that the document must already have an icon. This opens the icon
/// picker
Future<void> tapOnIconWidget() async {
final iconWidget = find.byType(EmojiIconWidget);
await tester.tapButton(iconWidget);
}
Future<void> tapOnAddCover() async {
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_addCover.tr(),
);
}
Future<void> tapOnChangeCover() async {
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_changeCover.tr(),
);
}
Future<void> switchSolidColorBackground() async {
final findPurpleButton = find.byWidgetPredicate(
(widget) => widget is ColorItem && widget.option.colorHex == "ffe8e0ff",
);
await tester.tapButton(findPurpleButton);
}
Future<void> addNetworkImageCover(String imageUrl) async {
final findNewImageButton = find.byType(NewCustomCoverButton);
await tester.tapButton(findNewImageButton);
final imageUrlTextField = find.descendant(
of: find.byType(NetworkImageUrlInput),
matching: find.byType(TextField),
);
await tester.enterText(imageUrlTextField, imageUrl);
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_add.tr(),
);
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_saveToGallery.tr(),
);
}
Future<void> switchNetworkImageCover(String imageUrl) async {
final image = find.byWidgetPredicate(
(widget) => widget is ImageGridItem,
);
await tester.tapButton(image);
}
Future<void> tapOnRemoveCover() async {
await tester.tapButton(find.byType(DeleteCoverButton));
}
/// A cover must be present in the document to function properly since this
/// catches all cover types collectively
Future<void> hoverOnCover() async {
final cover = find.byType(DocumentCover);
await tester.startGesture(
tester.getCenter(cover),
kind: PointerDeviceKind.mouse,
);
await tester.pumpAndSettle();
}
Future<void> dismissCoverPicker() async {
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
await tester.pumpAndSettle();
}
/// trigger the slash command (selection menu)
@@ -35,6 +141,11 @@ class EditorOperations {
await tester.ime.insertCharacter('/');
}
/// trigger the slash command (selection menu)
Future<void> showAtMenu() async {
await tester.ime.insertCharacter('@');
}
/// Tap the slash menu item with [name]
///
/// Must call [showSlashMenu] first.
@@ -42,4 +153,25 @@ class EditorOperations {
final slashMenuItem = find.text(name, findRichText: true);
await tester.tapButton(slashMenuItem);
}
/// Tap the at menu item with [name]
///
/// Must call [showAtMenu] first.
Future<void> tapAtMenuItemWithName(String name) async {
final atMenuItem = find.descendant(
of: find.byType(SelectionMenuWidget),
matching: find.text(name, findRichText: true),
);
await tester.tapButton(atMenuItem);
}
/// Update the editor's selection
Future<void> updateSelection(Selection selection) async {
final editorState = getCurrentEditorState();
editorState.updateSelectionWithReason(
selection,
reason: SelectionUpdateReason.uiEvent,
);
await tester.pumpAndSettle(const Duration(milliseconds: 200));
}
}
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'base.dart';
extension EmojiTestExtension on WidgetTester {
/// Must call [openEmojiPicker] first
Future<void> switchToEmojiList() async {
final icon = find.byIcon(Icons.tag_faces);
await tapButton(icon);
}
Future<void> tapEmoji(String emoji) async {
final emojiWidget = find.text(emoji);
await tapButton(emojiWidget);
}
}
@@ -1,29 +1,54 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_test/flutter_test.dart';
const String readme = 'Read me';
// const String readme = 'Read me';
const String gettingStated = '⭐️ Getting started';
extension Expectation on WidgetTester {
/// Expect to see the home page and with a default read me page.
void expectToSeeHomePage() {
expect(find.byType(HomeStack), findsOneWidget);
expect(find.textContaining(readme), findsOneWidget);
expect(find.textContaining(gettingStated), findsWidgets);
}
/// Expect to see the page name on the home page.
void expectToSeePageName(String name) {
final pageName = findPageName(name);
void expectToSeePageName(
String name, {
String? parentName,
ViewLayoutPB layout = ViewLayoutPB.Document,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
final pageName = findPageName(
name,
layout: layout,
parentName: parentName,
parentLayout: parentLayout,
);
expect(pageName, findsOneWidget);
}
/// Expect not to see the page name on the home page.
void expectNotToSeePageName(String name) {
final pageName = findPageName(name);
void expectNotToSeePageName(
String name, {
String? parentName,
ViewLayoutPB layout = ViewLayoutPB.Document,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
final pageName = findPageName(
name,
layout: layout,
parentName: parentName,
parentLayout: parentLayout,
);
expect(pageName, findsNothing);
}
@@ -42,12 +67,12 @@ extension Expectation on WidgetTester {
final exportSuccess = find.byWidgetPredicate(
(widget) =>
widget is FlowyText &&
widget.title == LocaleKeys.settings_files_exportFileSuccess.tr(),
widget.text == LocaleKeys.settings_files_exportFileSuccess.tr(),
);
expect(exportSuccess, findsOneWidget);
}
/// Expect to see the add button and icon button inside the document.
/// Expect to see the add button and icon button in the cover toolbar
void expectToSeePluginAddCoverAndIconButton() {
final addCover = find.textContaining(
LocaleKeys.document_plugins_cover_addCover.tr(),
@@ -59,10 +84,55 @@ extension Expectation on WidgetTester {
expect(addIcon, findsOneWidget);
}
/// Expect to see the document header toolbar empty
void expectToSeeEmptyDocumentHeaderToolbar() {
final addCover = find.textContaining(
LocaleKeys.document_plugins_cover_addCover.tr(),
);
final addIcon = find.textContaining(
LocaleKeys.document_plugins_cover_addIcon.tr(),
);
expect(addCover, findsNothing);
expect(addIcon, findsNothing);
}
void expectToSeeDocumentIcon(String? emoji) {
if (emoji == null) {
final iconWidget = find.byType(EmojiIconWidget);
expect(iconWidget, findsNothing);
return;
}
final iconWidget = find.byWidgetPredicate(
(widget) => widget is EmojiIconWidget && widget.emoji == emoji,
);
expect(iconWidget, findsOneWidget);
}
void expectToSeeDocumentCover(CoverType type) {
final findCover = find.byWidgetPredicate(
(widget) => widget is DocumentCover && widget.coverType == type,
);
expect(findCover, findsOneWidget);
}
void expectToSeeNoDocumentCover() {
final findCover = find.byType(DocumentCover);
expect(findCover, findsNothing);
}
void expectChangeCoverAndDeleteButton() {
final findChangeCover = find.text(
LocaleKeys.document_plugins_cover_changeCover.tr(),
);
final findRemoveIcon = find.byType(DeleteCoverButton);
expect(findChangeCover, findsOneWidget);
expect(findRemoveIcon, findsOneWidget);
}
/// Expect to see the user name on the home page
void expectToSeeUserName(String name) {
final userName = find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == name,
(widget) => widget is FlowyText && widget.text == name,
);
expect(userName, findsOneWidget);
}
@@ -72,16 +142,55 @@ extension Expectation on WidgetTester {
Finder textWidget = find.textContaining(text, findRichText: true);
if (textWidget.evaluate().isEmpty) {
textWidget = find.byWidgetPredicate(
(widget) => widget is FlowyText && widget.title == text,
(widget) => widget is FlowyText && widget.text == text,
);
}
expect(textWidget, findsOneWidget);
}
/// Find the page name on the home page.
Finder findPageName(String name) {
/// Find if the page is favorite
Finder findFavoritePageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
return find.byWidgetPredicate(
(widget) => widget is ViewSectionItem && widget.view.name == name,
(widget) =>
widget is ViewItem &&
widget.view.isFavorite &&
widget.categoryType == FolderCategoryType.favorite &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
);
}
Finder findPageName(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
String? parentName,
ViewLayoutPB parentLayout = ViewLayoutPB.Document,
}) {
if (parentName == null) {
return find.byWidgetPredicate(
(widget) =>
widget is SingleInnerViewItem &&
widget.view.name == name &&
widget.view.layout == layout,
skipOffstage: false,
);
}
return find.descendant(
of: find.byWidgetPredicate(
(widget) =>
widget is ViewItem &&
widget.view.name == parentName &&
widget.view.layout == parentLayout,
skipOffstage: false,
),
matching: findPageName(name, layout: layout),
);
}
}
@@ -9,11 +9,11 @@ extension IME on WidgetTester {
class IMESimulator {
IMESimulator(this.tester) {
client = findDeltaTextInputClient();
client = findTextInputClient();
}
final WidgetTester tester;
late final DeltaTextInputClient client;
late final TextInputClient client;
Future<void> insertText(String text) async {
for (final c in text.characters) {
@@ -27,28 +27,22 @@ class IMESimulator {
assert(false);
return;
}
final deltas = [
TextEditingDeltaInsertion(
textInserted: character,
oldText: value.text.replaceRange(
value.selection.start,
value.selection.end,
'',
),
insertionOffset: value.selection.baseOffset,
selection: TextSelection.collapsed(
offset: value.selection.baseOffset + 1,
),
composing: TextRange.empty,
final text = value.text
.replaceRange(value.selection.start, value.selection.end, character);
final textEditingValue = TextEditingValue(
text: text,
selection: TextSelection.collapsed(
offset: value.selection.baseOffset + 1,
),
];
client.updateEditingValueWithDeltas(deltas);
composing: TextRange.empty,
);
client.updateEditingValue(textEditingValue);
await tester.pumpAndSettle();
}
DeltaTextInputClient findDeltaTextInputClient() {
TextInputClient findTextInputClient() {
final finder = find.byType(KeyboardServiceWidget);
final KeyboardServiceWidgetState state = tester.state(finder);
return state.textInputService as DeltaTextInputClient;
return state.textInputService as TextInputClient;
}
}
@@ -1,10 +1,5 @@
import 'dart:io';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:file_picker/file_picker.dart' as fp;
import 'package:path/path.dart' as p;
import '../util.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
class MockFilePicker implements FilePickerService {
MockFilePicker({
@@ -25,7 +20,7 @@ class MockFilePicker implements FilePickerService {
String? dialogTitle,
String? fileName,
String? initialDirectory,
fp.FileType type = fp.FileType.any,
FileType type = FileType.any,
List<String>? allowedExtensions,
bool lockParentWindow = false,
}) {
@@ -36,18 +31,17 @@ class MockFilePicker implements FilePickerService {
Future<FilePickerResult?> pickFiles({
String? dialogTitle,
String? initialDirectory,
fp.FileType type = fp.FileType.any,
FileType type = FileType.any,
List<String>? allowedExtensions,
Function(fp.FilePickerStatus p1)? onFileLoading,
Function(FilePickerStatus p1)? onFileLoading,
bool allowCompression = true,
bool allowMultiple = false,
bool withData = false,
bool withReadStream = false,
bool lockParentWindow = false,
}) {
final platformFiles = mockPaths
.map((e) => fp.PlatformFile(path: e, name: '', size: 0))
.toList();
final platformFiles =
mockPaths.map((e) => PlatformFile(path: e, name: '', size: 0)).toList();
return Future.value(
FilePickerResult(
platformFiles,
@@ -56,20 +50,21 @@ class MockFilePicker implements FilePickerService {
}
}
Future<void> mockGetDirectoryPath(String? name) async {
final dir = await TestFolder.testLocation(name);
Future<void> mockGetDirectoryPath(
String path,
) async {
getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>(
() => MockFilePicker(
mockPath: dir.path,
mockPath: path,
),
);
return;
}
Future<String> mockSaveFilePath(String? name, String fileName) async {
final dir = await TestFolder.testLocation(name);
final path = p.join(dir.path, fileName);
Future<String> mockSaveFilePath(
String path,
) async {
getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>(
() => MockFilePicker(
@@ -79,18 +74,9 @@ Future<String> mockSaveFilePath(String? name, String fileName) async {
return path;
}
Future<List<String>> mockPickFilePaths(
List<String> fileNames, {
String? name,
String? customPath,
Future<List<String>> mockPickFilePaths({
required List<String> paths,
}) async {
late final Directory dir;
if (customPath != null) {
dir = Directory(customPath);
} else {
dir = await TestFolder.testLocation(name);
}
final paths = fileNames.map((e) => p.join(dir.path, e)).toList();
getIt.unregister<FilePickerService>();
getIt.registerFactory<FilePickerService>(
() => MockFilePicker(
@@ -0,0 +1,110 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter_test/flutter_test.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:url_launcher_platform_interface/link.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
class MockUrlLauncher extends Fake
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {
String? url;
PreferredLaunchMode? launchMode;
bool? useSafariVC;
bool? useWebView;
bool? enableJavaScript;
bool? enableDomStorage;
bool? universalLinksOnly;
Map<String, String>? headers;
String? webOnlyWindowName;
bool? response;
bool closeWebViewCalled = false;
bool canLaunchCalled = false;
bool launchCalled = false;
// ignore: use_setters_to_change_properties
void setCanLaunchExpectations(String url) {
this.url = url;
}
void setLaunchExpectations({
required String url,
PreferredLaunchMode? launchMode,
bool? useSafariVC,
bool? useWebView,
required bool enableJavaScript,
required bool enableDomStorage,
required bool universalLinksOnly,
required Map<String, String> headers,
required String? webOnlyWindowName,
}) {
this.url = url;
this.launchMode = launchMode;
this.useSafariVC = useSafariVC;
this.useWebView = useWebView;
this.enableJavaScript = enableJavaScript;
this.enableDomStorage = enableDomStorage;
this.universalLinksOnly = universalLinksOnly;
this.headers = headers;
this.webOnlyWindowName = webOnlyWindowName;
}
// ignore: use_setters_to_change_properties
void setResponse(bool response) {
this.response = response;
}
@override
LinkDelegate? get linkDelegate => null;
@override
Future<bool> canLaunch(String url) async {
expect(url, this.url);
canLaunchCalled = true;
return response!;
}
@override
Future<bool> launch(
String url, {
required bool useSafariVC,
required bool useWebView,
required bool enableJavaScript,
required bool enableDomStorage,
required bool universalLinksOnly,
required Map<String, String> headers,
String? webOnlyWindowName,
}) async {
expect(url, this.url);
expect(useSafariVC, this.useSafariVC);
expect(useWebView, this.useWebView);
expect(enableJavaScript, this.enableJavaScript);
expect(enableDomStorage, this.enableDomStorage);
expect(universalLinksOnly, this.universalLinksOnly);
expect(headers, this.headers);
expect(webOnlyWindowName, this.webOnlyWindowName);
launchCalled = true;
return response!;
}
@override
Future<bool> launchUrl(String url, LaunchOptions options) async {
expect(url, this.url);
expect(options.mode, launchMode);
expect(options.webViewConfiguration.enableJavaScript, enableJavaScript);
expect(options.webViewConfiguration.enableDomStorage, enableDomStorage);
expect(options.webViewConfiguration.headers, headers);
expect(options.webOnlyWindowName, webOnlyWindowName);
launchCalled = true;
return response!;
}
@override
Future<void> closeWebView() async {
closeWebViewCalled = true;
}
}
@@ -4,3 +4,5 @@ export 'settings.dart';
export 'data.dart';
export 'expectation.dart';
export 'editor_test_operations.dart';
export 'mock/mock_url_launcher.dart';
export 'ime.dart';
@@ -21,6 +21,6 @@
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
<string>11.0</string>
</dict>
</plist>
+1 -1
View File
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+134 -52
View File
@@ -1,81 +1,163 @@
PODS:
- flowy_editor (0.0.1):
- Flutter
- flowy_infra_ui (0.0.1):
- app_links (0.0.1):
- Flutter
- appflowy_backend (0.0.1):
- Flutter
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- device_info_plus (0.0.1):
- Flutter
- DKImagePickerController/Core (4.3.4):
- DKImagePickerController/ImageDataManager
- DKImagePickerController/Resource
- DKImagePickerController/ImageDataManager (4.3.4)
- DKImagePickerController/PhotoGallery (4.3.4):
- DKImagePickerController/Core
- DKPhotoGallery
- DKImagePickerController/Resource (4.3.4)
- DKPhotoGallery (0.0.17):
- DKPhotoGallery/Core (= 0.0.17)
- DKPhotoGallery/Model (= 0.0.17)
- DKPhotoGallery/Preview (= 0.0.17)
- DKPhotoGallery/Resource (= 0.0.17)
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Core (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Preview
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Model (0.0.17):
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Preview (0.0.17):
- DKPhotoGallery/Model
- DKPhotoGallery/Resource
- SDWebImage
- SwiftyGif
- DKPhotoGallery/Resource (0.0.17):
- SDWebImage
- SwiftyGif
- file_picker (0.0.1):
- DKImagePickerController/PhotoGallery
- Flutter
- flowy_infra_ui (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_inappwebview (0.0.1):
- fluttertoast (0.0.2):
- Flutter
- flutter_inappwebview/Core (= 0.0.1)
- OrderedSet (~> 5.0)
- flutter_inappwebview/Core (0.0.1):
- Toast
- integration_test (0.0.1):
- Flutter
- OrderedSet (~> 5.0)
- flutter_keyboard_visibility (0.0.1):
- package_info_plus (0.4.5):
- Flutter
- image_picker (0.0.1):
- path_provider_foundation (0.0.1):
- Flutter
- OrderedSet (5.0.0)
- path_provider (0.0.1):
- FlutterMacOS
- ReachabilitySwift (5.0.0)
- rich_clipboard_ios (0.0.1):
- Flutter
- url_launcher (0.0.1):
- SDWebImage (5.14.2):
- SDWebImage/Core (= 5.14.2)
- SDWebImage/Core (5.14.2)
- shared_preferences_foundation (0.0.1):
- Flutter
- video_player (0.0.1):
- FlutterMacOS
- sign_in_with_apple (0.0.1):
- Flutter
- SwiftyGif (5.4.3)
- Toast (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- flowy_editor (from `.symlinks/plugins/flowy_editor/ios`)
- flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
- app_links (from `.symlinks/plugins/app_links/ios`)
- appflowy_backend (from `.symlinks/plugins/appflowy_backend/ios`)
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- flowy_infra_ui (from `.symlinks/plugins/flowy_infra_ui/ios`)
- Flutter (from `Flutter`)
- flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
- video_player (from `.symlinks/plugins/video_player/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
- OrderedSet
- DKImagePickerController
- DKPhotoGallery
- ReachabilitySwift
- SDWebImage
- SwiftyGif
- Toast
EXTERNAL SOURCES:
flowy_editor:
:path: ".symlinks/plugins/flowy_editor/ios"
flowy_infra_ui:
:path: ".symlinks/plugins/flowy_infra_ui/ios"
app_links:
:path: ".symlinks/plugins/app_links/ios"
appflowy_backend:
:path: ".symlinks/plugins/appflowy_backend/ios"
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
flowy_infra_ui:
:path: ".symlinks/plugins/flowy_infra_ui/ios"
Flutter:
:path: Flutter
flutter_inappwebview:
:path: ".symlinks/plugins/flutter_inappwebview/ios"
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
url_launcher:
:path: ".symlinks/plugins/url_launcher/ios"
video_player:
:path: ".symlinks/plugins/video_player/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
rich_clipboard_ios:
:path: ".symlinks/plugins/rich_clipboard_ios/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sign_in_with_apple:
:path: ".symlinks/plugins/sign_in_with_apple/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
flowy_editor: bf8d58894ddb03453bd4d8521c57267ad638b837
flowy_infra_ui: 146c88346fd55d2ee6a41ae35059a5bf095cfbb3
appflowy_backend: c416222c639e678828776789bf0c1a1d0d59df3c
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_inappwebview: bfd58618f49dc62f2676de690fc6dcda1d6c3721
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6
OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
video_player: ecd305f42e9044793efd34846e1ce64c31ea6fcb
app_links: 5ef33d0d295a89d9d16bb81b0e3b0d5f70d6c875
appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
file_picker: ce3938a0df3cc1ef404671531facef740d03f920
flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
integration_test: 13825b8a9334a850581300559b8839134b124670
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
rich_clipboard_ios: 7588abe18f881a6d0e9ec0b12e51cae2761e8942
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
COCOAPODS: 1.10.1
COCOAPODS: 1.11.3
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -217,10 +217,12 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -231,6 +233,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -340,7 +343,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -414,7 +417,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -463,7 +466,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -47,5 +47,7 @@
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
@@ -1,38 +0,0 @@
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
class Config {
static Future<void> setSupabaseConfig({
required String url,
required String anonKey,
required String key,
required String secret,
}) async {
await ConfigEventSetSupabaseConfig(
SupabaseConfigPB.create()
..supabaseUrl = url
..key = key
..anonKey = anonKey
..jwtSecret = secret,
).send();
}
static Future<void> setSupabaseCollabPluginConfig({
required String url,
required String key,
required String jwtSecret,
required String collabTable,
}) async {
final payload = CollabPluginConfigPB.create();
final collabTableConfig = CollabTableConfigPB.create()
..tableName = collabTable;
payload.supabaseConfig = SupabaseDBConfigPB.create()
..supabaseUrl = url
..key = key
..jwtSecret = jwtSecret
..collabTableConfig = collabTableConfig;
await ConfigEventSetCollabPluginConfig(payload).send();
}
}
@@ -18,4 +18,27 @@ class KVKeys {
/// The value is a json string with the following format:
/// {'height': 600.0, 'width': 800.0}
static const String windowSize = 'windowSize';
/// The key for saving the window position
///
/// The value is a json string with the following format:
/// {'dx': 10.0, 'dy': 10.0}
static const String windowPosition = 'windowPosition';
static const String kDocumentAppearanceFontSize =
'kDocumentAppearanceFontSize';
static const String kDocumentAppearanceFontFamily =
'kDocumentAppearanceFontFamily';
/// The key for saving the expanded views
///
/// The value is a json string with the following format:
/// {'viewId': true, 'viewId2': false}
static const String expandedViews = 'expandedViews';
/// The key for saving the expanded folder
///
/// The value is a json string with the following format:
/// {'SidebarFolderCategoryType.value': true}
static const String expandedFolders = 'expandedFolders';
}
@@ -1,11 +1,12 @@
import 'package:flutter/foundation.dart' show TargetPlatform;
import 'package:flutter/foundation.dart' show TargetPlatform, kIsWeb;
extension TargetPlatformHelper on TargetPlatform {
/// Convenience function to check if the app is running on a desktop computer.
///
/// Easily check if on desktop by checking `defaultTargetPlatform.isDesktop`.
bool get isDesktop =>
this == TargetPlatform.linux ||
this == TargetPlatform.macOS ||
this == TargetPlatform.windows;
!kIsWeb &&
(this == TargetPlatform.linux ||
this == TargetPlatform.macOS ||
this == TargetPlatform.windows);
}
@@ -1,9 +1,9 @@
import 'dart:async';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-net/network_state.pb.dart';
import 'package:flutter/services.dart';
class NetworkListener {
@@ -46,12 +46,12 @@ class NetworkListener {
return NetworkTypePB.VPN;
case ConnectivityResult.none:
case ConnectivityResult.other:
return NetworkTypePB.Unknown;
return NetworkTypePB.NetworkUnknown;
}
}();
Log.info("Network type: $networkType");
final state = NetworkStatePB.create()..ty = networkType;
NetworkEventUpdateNetworkType(state).send().then((result) {
UserEventUpdateNetworkState(state).send().then((result) {
result.fold(
(l) {},
(e) => Log.error(e),
@@ -0,0 +1,11 @@
import 'package:flutter/services.dart';
extension RawKeyboardExtension on RawKeyboard {
bool get isAltPressed => keysPressed.any(
(key) => [
LogicalKeyboardKey.alt,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
].contains(key),
);
}
+23 -18
View File
@@ -1,8 +1,20 @@
// lib/env/env.dart
import 'package:appflowy/startup/startup.dart';
import 'package:envied/envied.dart';
part 'env.g.dart';
/// The environment variables are defined in `.env` file that is located in the
/// appflowy_flutter.
/// Run `dart run build_runner build --delete-conflicting-outputs`
/// to generate the keys from the env file.
///
/// If you want to regenerate the keys, you need to run `dart run
/// build_runner clean` before running `dart run build_runner build
/// --delete-conflicting-outputs`.
/// Follow the guide on https://supabase.com/docs/guides/auth/social-login/auth-google to setup the auth provider.
///
@Envied(path: '.env')
abstract class Env {
@EnviedField(
@@ -17,29 +29,22 @@ abstract class Env {
defaultValue: '',
)
static final String supabaseAnonKey = _Env.supabaseAnonKey;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_KEY',
defaultValue: '',
)
static final String supabaseKey = _Env.supabaseKey;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_JWT_SECRET',
defaultValue: '',
)
static final String supabaseJwtSecret = _Env.supabaseJwtSecret;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_COLLAB_TABLE',
defaultValue: '',
)
static final String supabaseCollabTable = _Env.supabaseCollabTable;
}
bool get isSupabaseEnable =>
Env.supabaseUrl.isNotEmpty &&
Env.supabaseAnonKey.isNotEmpty &&
Env.supabaseKey.isNotEmpty &&
Env.supabaseJwtSecret.isNotEmpty;
bool get isSupabaseEnabled {
// Only enable supabase in release and develop mode.
if (integrationEnv().isRelease || integrationEnv().isDevelop) {
return Env.supabaseUrl.isNotEmpty &&
Env.supabaseAnonKey.isNotEmpty &&
Env.supabaseJwtSecret.isNotEmpty;
} else {
return false;
}
}
+4 -1
View File
@@ -6,5 +6,8 @@ import 'startup/startup.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await FlowyRunner.run(FlowyApp());
await FlowyRunner.run(
FlowyApp(),
integrationEnv(),
);
}
@@ -43,7 +43,11 @@ class BlankPagePluginWidgetBuilder extends PluginWidgetBuilder
Widget get leftBarItem => FlowyText.medium(LocaleKeys.blankPageTitle.tr());
@override
Widget buildWidget({PluginContext? context}) => const BlankPage();
Widget tabBarItem(String pluginId) => leftBarItem;
@override
Widget buildWidget({PluginContext? context, required bool shrinkWrap}) =>
const BlankPage();
@override
List<NavigationItem> get navigationItems => [this];
@@ -24,38 +24,38 @@ class CellCacheKey {
/// We use GridCellCacheKey to index the cell in the cache.
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid
/// for more information
class CellCache {
class CellMemCache {
final String viewId;
/// fieldId: {cacheKey: GridCell}
final Map<String, Map<RowId, dynamic>> _cellDataByFieldId = {};
CellCache({
final Map<String, Map<RowId, dynamic>> _cellByFieldId = {};
CellMemCache({
required this.viewId,
});
void removeCellWithFieldId(String fieldId) {
_cellDataByFieldId.remove(fieldId);
_cellByFieldId.remove(fieldId);
}
void remove(CellCacheKey key) {
final map = _cellDataByFieldId[key.fieldId];
final map = _cellByFieldId[key.fieldId];
if (map != null) {
map.remove(key.rowId);
}
}
void insert<T extends DatabaseCell>(CellCacheKey key, T value) {
var map = _cellDataByFieldId[key.fieldId];
var map = _cellByFieldId[key.fieldId];
if (map == null) {
_cellDataByFieldId[key.fieldId] = {};
map = _cellDataByFieldId[key.fieldId];
_cellByFieldId[key.fieldId] = {};
map = _cellByFieldId[key.fieldId];
}
map![key.rowId] = value.object;
}
T? get<T>(CellCacheKey key) {
final map = _cellDataByFieldId[key.fieldId];
final map = _cellByFieldId[key.fieldId];
if (map == null) {
return null;
} else {
@@ -72,6 +72,6 @@ class CellCache {
}
Future<void> dispose() async {
_cellDataByFieldId.clear();
_cellByFieldId.clear();
}
}
@@ -24,7 +24,7 @@ import 'cell_service.dart';
// ignore: must_be_immutable
class CellController<T, D> extends Equatable {
DatabaseCellContext _cellContext;
final CellCache _cellCache;
final CellMemCache _cellCache;
final CellCacheKey _cacheKey;
final FieldBackendService _fieldBackendSvc;
final CellDataLoader<T> _cellDataLoader;
@@ -54,7 +54,7 @@ class CellController<T, D> extends Equatable {
CellController({
required DatabaseCellContext cellContext,
required CellCache cellCache,
required CellMemCache cellCache,
required CellDataLoader<T> cellDataLoader,
required CellDataPersistence<D> cellDataPersistence,
}) : _cellContext = cellContext,
@@ -103,12 +103,15 @@ class CellController<T, D> extends Equatable {
},
);
_rowMetaListener?.start(
callback: (newRowMeta) {
_cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
_onRowMetaChanged?.call();
},
);
// Only the primary can listen on the row meta changes.
if (_cellContext.fieldInfo.isPrimary) {
_rowMetaListener?.start(
callback: (newRowMeta) {
_cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
_onRowMetaChanged?.call();
},
);
}
}
/// Listen on the cell content or field changes
Loaded 100 of 1089 files, more files were not shown because too many files have changed in this diff. Show more