Compare commits

...
62 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
782 changed files with 31690 additions and 11926 deletions

No files matched your search

+1
View File
@@ -134,6 +134,7 @@ jobs:
fail_ci_if_error: true
verbose: true
os: ${{ matrix.os }}
token: ${{ secrets.CODECOV_TOKEN }}
attempt_limit: 20
attempt_delay: 10000
+1
View File
@@ -136,5 +136,6 @@ jobs:
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
+29
View File
@@ -1,5 +1,34 @@
# Release Notes
## 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
+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/"
// },
]
}
+31 -4
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}"
+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.6"
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"
+2
View File
@@ -75,3 +75,5 @@ windows/flutter/dart_ffi/
coverage/
**/failures/*.png
assets/translations/
@@ -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

@@ -1,154 +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"
},
"moreAction.small": "petit"
}
@@ -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,232 +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"
}
},
"moreAction.small": "pequeño"
}
@@ -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,154 +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"
},
"moreAction.small": "petit"
}
@@ -1,240 +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"
}
},
"moreAction.small": "petit"
}
@@ -1,155 +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-lal",
"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 írd be a fenti kódot:",
"instruction4": "Nyomd meg az alábbi gombot, ha elvégezted a regisztrációt:"
}
},
"settings": {
"title": "Beállítások",
"menu": {
"appearance": "Megjelenés",
"language": "Nyelv",
"open": "Beállítások megnyitása"
},
"appearance": {
"themeMode": {
"label": "Téma",
"light": "Világos mód",
"dark": "Éjjeli mód",
"system": "Rendszerbeállítás követése"
}
}
},
"sideBar": {
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
},
"moreAction.small": "kicsi"
}
@@ -1,227 +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"
},
"moreAction.small": "kecil"
}
@@ -1,160 +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"
},
"moreAction.small": "piccolo"
}
@@ -1,208 +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"
},
"moreAction.small": "小さい"
}
@@ -1,236 +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": "추가"
}
},
"moreAction.small": "작은"
}
@@ -1,154 +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"
},
"moreAction.small": "mały"
}
@@ -1,150 +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"
},
"moreAction.small": "pequeno"
}
@@ -1,240 +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"
}
},
"moreAction.small": "små"
}
@@ -1,154 +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"
},
"moreAction.small": "küçük"
}
@@ -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();
}
@@ -14,8 +14,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// open setting
await tester.tapDatabaseSettingButton();
@@ -36,7 +35,11 @@ void main() {
await tester.tapGoButton();
// Create calendar view
await tester.createNewPageWithName(ViewLayoutPB.Calendar, 'calendar');
const name = 'calendar';
await tester.createNewPageWithName(
name: name,
layout: ViewLayoutPB.Calendar,
);
// Open setting
await tester.tapDatabaseSettingButton();
@@ -47,9 +50,9 @@ void main() {
await tester.tapFirstDayOfWeekStartFromMonday();
// Open the other page and open the new calendar page again
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.pumpAndSettle(const Duration(milliseconds: 300));
await tester.openPage('calendar');
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
// Open setting again and check the start from Monday is selected
await tester.tapDatabaseSettingButton();
@@ -65,8 +68,7 @@ void main() {
await tester.tapGoButton();
// Create the calendar view
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Scroll until today's date cell is visible
await tester.scrollToToday();
@@ -135,8 +137,7 @@ void main() {
await tester.tapGoButton();
// Create the calendar view
await tester.tapAddButton();
await tester.tapCreateCalendarButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Calendar);
// Create a new event on the first of this month
final today = DateTime.now();
@@ -15,8 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
await tester.editCell(
rowIndex: 0,
@@ -38,7 +37,10 @@ void main() {
testWidgets('edit multiple text cells', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.createNewPageWithName(ViewLayoutPB.Grid, 'my grid');
await tester.createNewPageWithName(
name: 'my grid',
layout: ViewLayoutPB.Grid,
);
await tester.createField(FieldType.RichText, 'description');
await tester.editCell(
@@ -75,8 +77,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.Number;
@@ -134,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);
@@ -153,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
@@ -172,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
@@ -191,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);
@@ -268,6 +265,18 @@ void main() {
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();
});
@@ -276,9 +285,9 @@ void main() {
await tester.tapGoButton();
const fieldType = FieldType.SingleSelect;
await tester.tapAddButton();
// When create a grid, it will create a single select field by default
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Tap the cell to invoke the selection option editor
await tester.tapSelectOptionCellInGrid(rowIndex: 0, fieldType: fieldType);
@@ -354,8 +363,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
const fieldType = FieldType.MultiSelect;
await tester.createField(fieldType, fieldType.name);
@@ -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';
@@ -14,8 +17,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('Name');
@@ -32,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');
@@ -55,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');
@@ -70,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');
@@ -91,8 +90,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));
@@ -112,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));
@@ -133,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();
@@ -154,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,
@@ -182,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);
});
});
}
@@ -9,7 +9,7 @@ import 'util/database_test_op.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('grid', () {
group('database filter', () {
testWidgets('add text filter', (tester) async {
await tester.openV020database();
@@ -1,13 +1,14 @@
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/emoji.dart';
import 'util/ime.dart';
import 'util/util.dart';
void main() {
@@ -19,8 +20,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();
@@ -34,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();
@@ -55,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();
@@ -85,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();
@@ -108,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();
@@ -144,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();
@@ -160,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();
@@ -194,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();
@@ -216,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';
@@ -12,8 +13,7 @@ void main() {
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
@@ -25,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();
@@ -41,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
@@ -60,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';
@@ -13,8 +14,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();
@@ -31,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();
@@ -10,22 +10,22 @@ void main() {
group('grid', () {
testWidgets('add text sort', (tester) async {
await tester.openV020database();
// create a filter
// 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(
@@ -142,7 +142,6 @@ void main() {
// check the number cell order
for (final (index, content) in <String>[
'',
'-2',
'-1',
'0.1',
@@ -152,6 +151,7 @@ void main() {
'10',
'11',
'12',
'',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
@@ -186,53 +186,74 @@ void main() {
await tester.pumpAndSettle();
});
testWidgets('add number and text sort', (tester) async {
testWidgets('add checkbox and number sort', (tester) async {
await tester.openV020database();
// create a filter
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.Number, 'number');
await tester.tapCreateSortByFieldType(FieldType.Checkbox, 'Done');
// open the sort menu and select number order by descending
// open the sort menu and sort checkbox by descending
await tester.tapSortMenuInSettingBar();
await tester.tapSortButtonByName('number');
await tester.tapSortButtonByName('Done');
await tester.tapSortByDescending();
for (final (index, content) in <String>[
'12',
'11',
'10',
'2',
'1',
'0.2',
'0.1',
'-1',
'-2',
'',
for (final (index, content) in <bool>[
true,
true,
true,
true,
true,
false,
false,
false,
false,
false,
].indexed) {
await tester.assertCellContent(
await tester.assertCheckboxCell(
rowIndex: index,
fieldType: FieldType.Number,
content: content,
isSelected: content,
);
}
// add another sort, this time by number descending
await tester.tapSortMenuInSettingBar();
await tester.tapCreateSortByFieldTypeInSortMenu(
FieldType.RichText,
'Name',
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',
'',
'-1',
'-2',
'0.1',
'0.2',
'1',
].indexed) {
await tester.assertCellContent(
rowIndex: index,
@@ -241,26 +262,6 @@ void main() {
);
}
// check 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();
});
});
@@ -15,8 +15,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -37,8 +36,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -63,8 +61,7 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.tapAddButton();
await tester.tapCreateGridButton();
await tester.createNewPageWithName(layout: ViewLayoutPB.Grid);
// Create board view
await tester.tapCreateLinkedDatabaseViewButton(AddButtonAction.board);
@@ -17,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(
@@ -35,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',
@@ -57,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);
});
});
}
@@ -7,7 +7,9 @@ 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();
@@ -19,4 +21,6 @@ void startTesting() {
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();
}
@@ -1,9 +1,12 @@
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';
@@ -61,6 +64,54 @@ void main() {
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,
);
});
});
}
@@ -73,13 +124,15 @@ Future<void> insertReferenceDatabase(
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_reference_${layout.name}',
name: 'insert_a_reference_${layout.name}',
layout: ViewLayoutPB.Document,
openAfterCreated: true,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
@@ -98,3 +151,38 @@ Future<void> insertReferenceDatabase(
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);
}
@@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/ime.dart';
import '../util/util.dart';
void main() {
@@ -22,8 +21,8 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -68,8 +67,8 @@ void main() {
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
name: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
@@ -99,12 +98,12 @@ void main() {
// expect to the see the inline math equation button is highlighted
inlineMathEquationButton = find.byWidgetPredicate(
(widget) =>
widget is IconItemWidget &&
widget is SVGIconItemWidget &&
widget.tooltip ==
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
expect(
tester.widget<IconItemWidget>(inlineMathEquationButton).isHighlight,
tester.widget<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
isTrue,
);
@@ -62,9 +62,11 @@ void main() {
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Document);
// rename
await tester.hoverOnPageName(pageName);
const newName = 'RenameToNewPageName';
await tester.renamePage(newName);
await tester.hoverOnPageName(
pageName,
onHover: () async => await tester.renamePage(newName),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(newName),
@@ -79,8 +81,11 @@ void main() {
final pageName = await insertingInlinePage(tester, ViewLayoutPB.Grid);
// rename
await tester.hoverOnPageName(pageName);
await tester.tapDeletePageButton();
await tester.hoverOnPageName(
pageName,
layout: ViewLayoutPB.Grid,
onHover: () async => await tester.tapDeletePageButton(),
);
final finder = find.descendant(
of: find.byType(MentionPageBlock),
matching: find.findTextInFlowyText(pageName),
@@ -101,13 +106,14 @@ Future<String> insertingInlinePage(
final id = uuid();
final name = '${layout.name}_$id';
await tester.createNewPageWithName(
layout,
name,
name: name,
layout: layout,
openAfterCreated: false,
);
// create a new document
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'insert_a_inline_page_${layout.name}',
name: 'insert_a_inline_page_${layout.name}',
layout: ViewLayoutPB.Document,
);
// tap the first line of the document
await tester.editor.tapLineOfEditorAt(0);
@@ -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);
});
});
}
@@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/ime.dart';
import '../util/util.dart';
void main() {
@@ -17,8 +16,8 @@ void main() {
await tester.tapGoButton();
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'outline_test',
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
@@ -34,8 +33,8 @@ void main() {
await tester.tapGoButton();
await tester.createNewPageWithName(
ViewLayoutPB.Document,
'outline_test',
name: 'outline_test',
layout: ViewLayoutPB.Document,
);
await tester.editor.tapLineOfEditorAt(0);
@@ -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();
});
});
}
@@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../util/ime.dart';
import '../util/util.dart';
void main() {
@@ -19,7 +18,10 @@ void main() {
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
@@ -57,7 +59,7 @@ void main() {
);
// switch to other page and switch back
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// the numbered list should be kept
@@ -73,7 +75,10 @@ void main() {
// create a new document called Sample
const pageName = 'Sample';
await tester.createNewPageWithName(ViewLayoutPB.Document, pageName);
await tester.createNewPageWithName(
name: pageName,
layout: ViewLayoutPB.Document,
);
// focus on the editor
await tester.editor.tapLineOfEditorAt(0);
@@ -86,7 +91,7 @@ void main() {
}
// switch to other page and switch back
await tester.openPage(readme);
await tester.openPage(gettingStated);
await tester.openPage(pageName);
// this screenshots are different on different platform, so comment it out temporarily.
@@ -99,7 +104,6 @@ void main() {
});
}
// TODO(Lucas.Xu): there're no shorctcuts for underline, format code yet.
const _sample = r'''
# Heading 1
## Heading 2
@@ -16,10 +16,10 @@ void main() {
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'];
@@ -14,6 +14,8 @@ import 'document/document_test_runner.dart' as document_test_runner;
import 'import_files_test.dart' as import_files_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.
///
@@ -31,6 +33,12 @@ void 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();
@@ -16,7 +16,7 @@ void main() {
await tester.tapGoButton();
// expect to see a readme page
tester.expectToSeePageName(readme);
tester.expectToSeePageName(gettingStated);
// mock the file picker
final path = await mockSaveFilePath(
@@ -42,12 +42,16 @@ void main() {
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 =
@@ -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();
}
@@ -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),
),
);
});
});
}
@@ -91,7 +91,6 @@ extension AppFlowyTestBase on WidgetTester {
warnIfMissed: warnIfMissed,
);
await pumpAndSettle(Duration(milliseconds: milliseconds));
return;
}
Future<void> tapButtonWithName(
@@ -1,15 +1,19 @@
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';
@@ -24,35 +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 [tapAddButton] first.
/// 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());
}
@@ -116,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);
@@ -133,19 +151,30 @@ 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) async {
final finder = findPageName(name);
Future<void> openPage(
String name, {
ViewLayoutPB layout = ViewLayoutPB.Document,
}) async {
final finder = findPageName(name, layout: layout);
expect(finder, findsOneWidget);
await tapButton(finder);
}
@@ -154,20 +183,32 @@ extension CommonOperations on WidgetTester {
///
/// 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.
@@ -224,21 +265,39 @@ 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
await hoverOnPageName(
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
onHover: () async {
await renamePage(name);
await pumpAndSettle();
},
);
await pumpAndSettle();
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(
@@ -276,6 +335,72 @@ extension CommonOperations on WidgetTester {
}
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(
name,
layout: layout,
useLast: true,
onHover: () async {
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();
}
}
extension ViewLayoutPBTest on ViewLayoutPB {
@@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database_view/calendar/application/calendar_blo
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_day.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/toolbar/calendar_layout_setting.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checkbox.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
@@ -14,31 +15,42 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/create_filter_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/disclosure_button.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_menu_item.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell_action_sheet.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_option_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/create_sort_list.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/order_panel.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_menu.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/select_option_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/text_field.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/date_cell/date_editor.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/database_setting.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:calendar_view/calendar_view.dart';
@@ -50,25 +62,12 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/footer/grid_footer.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/row.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_action.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_banner.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:path/path.dart' as p;
import 'package:table_calendar/table_calendar.dart';
import 'base.dart';
import 'common_operations.dart';
import 'expectation.dart';
import 'package:path/path.dart' as p;
import 'mock/mock_file_picker.dart';
extension AppFlowyDatabaseTest on WidgetTester {
@@ -77,9 +76,9 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapGoButton();
// expect to see a readme page
expectToSeePageName(readme);
expectToSeePageName(gettingStated);
await tapAddButton();
await tapAddViewButton();
await tapImportButton();
final testFileNames = ['v020.afdb'];
@@ -103,7 +102,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
paths: paths,
);
await tapDatabaseRawDataButton();
await openPage('v020');
await pumpAndSettle();
await openPage('v020', layout: ViewLayoutPB.Grid);
}
Future<void> hoverOnFirstRowOfGrid() async {
@@ -358,6 +358,16 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(findNewDateFormat);
}
Future<void> clearDate() async {
final findDateEditor = find.byType(DateCellEditor);
final findClearButton = find.byType(ClearDateButton);
final finder = find.descendant(
of: findDateEditor,
matching: findClearButton,
);
await tapButton(finder);
}
Future<void> tapSelectOptionCellInGrid({
required int rowIndex,
required FieldType fieldType,
@@ -1197,6 +1207,10 @@ extension AppFlowyDatabaseTest on WidgetTester {
Future<void> tapDatabaseRawDataButton() async {
await tapButtonWithName(LocaleKeys.importPanel_database.tr());
}
Future<void> tapAddSelectOptionButton() async {
await tapButtonWithName(LocaleKeys.grid_field_addSelectOption.tr());
}
}
Finder finderForDatabaseLayoutType(DatabaseLayoutPB layout) {
@@ -12,7 +12,6 @@ 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 {
@@ -2,30 +2,53 @@ 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), findsWidgets);
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);
}
@@ -125,11 +148,49 @@ extension Expectation on WidgetTester {
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),
);
}
}
@@ -77,13 +77,6 @@ Future<String> mockSaveFilePath(
Future<List<String>> mockPickFilePaths({
required List<String> paths,
}) async {
// late final Directory dir;
// if (customPath != null) {
// dir = Directory(customPath);
// } else {
// dir = await TestFolder.testLocation(applicationDataPath, 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,30 +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,
required String pgUrl,
required String pgUser,
required String pgPassword,
required String pgPort,
}) async {
final postgresConfig = PostgresConfigurationPB.create()
..url = pgUrl
..userName = pgUser
..password = pgPassword
..port = int.parse(pgPort);
await ConfigEventSetSupabaseConfig(
SupabaseConfigPB.create()
..supabaseUrl = url
..key = key
..anonKey = anonKey
..jwtSecret = secret
..postgresConfig = postgresConfig,
).send();
}
}
@@ -19,8 +19,26 @@ class KVKeys {
/// {'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),
);
}
+12 -43
View File
@@ -1,4 +1,5 @@
// lib/env/env.dart
import 'package:appflowy/startup/startup.dart';
import 'package:envied/envied.dart';
part 'env.g.dart';
@@ -28,54 +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_DB',
defaultValue: '',
)
static final String supabaseDb = _Env.supabaseDb;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_USER',
defaultValue: '',
)
static final String supabaseDbUser = _Env.supabaseDbUser;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_PASSWORD',
defaultValue: '',
)
static final String supabaseDbPassword = _Env.supabaseDbPassword;
@EnviedField(
obfuscate: true,
varName: 'SUPABASE_DB_PORT',
defaultValue: '5432',
)
static final String supabaseDbPort = _Env.supabaseDbPort;
}
bool get isSupabaseEnable =>
Env.supabaseUrl.isNotEmpty &&
Env.supabaseAnonKey.isNotEmpty &&
Env.supabaseKey.isNotEmpty &&
Env.supabaseJwtSecret.isNotEmpty &&
Env.supabaseDb.isNotEmpty &&
Env.supabaseDbUser.isNotEmpty &&
Env.supabaseDbPassword.isNotEmpty &&
Env.supabaseDbPort.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;
}
}
@@ -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
@@ -18,11 +18,11 @@ typedef URLCellController = CellController<URLCellDataPB, String>;
class CellControllerBuilder {
final DatabaseCellContext _cellContext;
final CellCache _cellCache;
final CellMemCache _cellCache;
CellControllerBuilder({
required DatabaseCellContext cellContext,
required CellCache cellCache,
required CellMemCache cellCache,
}) : _cellCache = cellCache,
_cellContext = cellContext;
@@ -35,6 +35,7 @@ class DateCellData with _$DateCellData {
DateTime? dateTime,
String? time,
required bool includeTime,
bool? clearFlag,
}) = _DateCellData;
}
@@ -55,6 +56,9 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
if (data.time != null) {
payload.time = data.time!;
}
if (data.clearFlag != null) {
payload.clearFlag = data.clearFlag!;
}
payload.includeTime = data.includeTime;
return DatabaseEventUpdateDateCell(payload).send().then((result) {
@@ -17,13 +17,13 @@ typedef OnDatabaseChanged = void Function(DatabasePB);
typedef OnRowsCreated = void Function(List<RowId> ids);
typedef OnRowsUpdated = void Function(
List<RowId> ids,
RowsChangedReason reason,
ChangedReason reason,
);
typedef OnRowsDeleted = void Function(List<RowId> ids);
typedef OnNumOfRowsChanged = void Function(
UnmodifiableListView<RowInfo> rows,
UnmodifiableMapView<RowId, RowInfo> rowByRowId,
RowsChangedReason reason,
ChangedReason reason,
);
typedef OnError = void Function(FlowyError);
@@ -687,27 +687,29 @@ class FieldController {
}
}
class RowDelegatesImpl extends RowFieldsDelegate with RowCacheDelegate {
final FieldController _cache;
class RowCacheDependenciesImpl extends RowFieldsDelegate with RowLifeCycle {
final FieldController _fieldController;
OnReceiveFields? _onFieldFn;
RowDelegatesImpl(FieldController cache) : _cache = cache;
RowCacheDependenciesImpl(FieldController cache) : _fieldController = cache;
@override
UnmodifiableListView<FieldInfo> get fields =>
UnmodifiableListView(_cache.fieldInfos);
UnmodifiableListView(_fieldController.fieldInfos);
@override
void onFieldsChanged(void Function(List<FieldInfo>) callback) {
_onFieldFn = (fieldInfos) {
callback(fieldInfos);
};
_cache.addListener(onReceiveFields: _onFieldFn);
if (_onFieldFn != null) {
_fieldController.removeListener(onFieldsListener: _onFieldFn!);
}
_onFieldFn = (fieldInfos) => callback(fieldInfos);
_fieldController.addListener(onReceiveFields: _onFieldFn);
}
@override
void onRowDispose() {
void onRowDisposed() {
if (_onFieldFn != null) {
_cache.removeListener(onFieldsListener: _onFieldFn!);
_fieldController.removeListener(onFieldsListener: _onFieldFn!);
_onFieldFn = null;
}
}
@@ -788,6 +790,7 @@ class FieldInfo {
case FieldType.RichText:
case FieldType.Checkbox:
case FieldType.Number:
case FieldType.DateTime:
return true;
default:
return false;
@@ -13,30 +13,25 @@ part 'row_cache.freezed.dart';
typedef RowUpdateCallback = void Function();
/// A delegate that provides the fields of the row.
abstract class RowFieldsDelegate {
UnmodifiableListView<FieldInfo> get fields;
void onFieldsChanged(void Function(List<FieldInfo>) callback);
}
abstract mixin class RowCacheDelegate {
UnmodifiableListView<FieldInfo> get fields;
void onRowDispose();
abstract mixin class RowLifeCycle {
void onRowDisposed();
}
/// Cache the rows in memory
/// Insert / delete / update row
///
/// Read https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/frontend/grid for more information.
class RowCache {
final String viewId;
/// _rows contains the current block's rows
/// Use List to reverse the order of the GridRow.
final RowList _rowList = RowList();
final CellCache _cellCache;
final RowCacheDelegate _delegate;
final RowChangesetNotifier _rowChangeReasonNotifier;
final CellMemCache _cellMemCache;
final RowLifeCycle _rowLifeCycle;
final RowFieldsDelegate _fieldDelegate;
final RowChangesetNotifier _changedNotifier;
/// Returns a unmodifiable list of RowInfo
UnmodifiableListView<RowInfo> get rowInfos {
@@ -44,29 +39,29 @@ class RowCache {
return UnmodifiableListView(visibleRows);
}
/// Returns a unmodifiable map of rowId to RowInfo
/// Returns a unmodifiable map of RowInfo
UnmodifiableMapView<RowId, RowInfo> get rowByRowId {
return UnmodifiableMapView(_rowList.rowInfoByRowId);
}
CellCache get cellCache => _cellCache;
RowsChangedReason get changeReason => _rowChangeReasonNotifier.reason;
CellMemCache get cellCache => _cellMemCache;
ChangedReason get changeReason => _changedNotifier.reason;
RowCache({
required this.viewId,
required RowFieldsDelegate fieldsDelegate,
required RowCacheDelegate cacheDelegate,
}) : _cellCache = CellCache(viewId: viewId),
_rowChangeReasonNotifier = RowChangesetNotifier(),
_delegate = cacheDelegate {
//
required RowLifeCycle rowLifeCycle,
}) : _cellMemCache = CellMemCache(viewId: viewId),
_changedNotifier = RowChangesetNotifier(),
_rowLifeCycle = rowLifeCycle,
_fieldDelegate = fieldsDelegate {
// Listen on the changed of the fields. If the fields changed, we need to
// clear the cell cache with the given field id.
fieldsDelegate.onFieldsChanged((fieldInfos) {
for (final fieldInfo in fieldInfos) {
_cellCache.removeCellWithFieldId(fieldInfo.id);
_cellMemCache.removeCellWithFieldId(fieldInfo.id);
}
_rowChangeReasonNotifier
.receive(const RowsChangedReason.fieldDidChange());
_changedNotifier.receive(const ChangedReason.fieldDidChange());
});
}
@@ -82,9 +77,9 @@ class RowCache {
}
Future<void> dispose() async {
_delegate.onRowDispose();
_rowChangeReasonNotifier.dispose();
await _cellCache.dispose();
_rowLifeCycle.onRowDisposed();
_changedNotifier.dispose();
await _cellMemCache.dispose();
}
void applyRowsChanged(RowsChangePB changeset) {
@@ -100,7 +95,7 @@ class RowCache {
void reorderAllRows(List<String> rowIds) {
_rowList.reorderWithRowIds(rowIds);
_rowChangeReasonNotifier.receive(const RowsChangedReason.reorderRows());
_changedNotifier.receive(const ChangedReason.reorderRows());
}
void reorderSingleRow(ReorderSingleRowPB reorderRow) {
@@ -111,8 +106,8 @@ class RowCache {
reorderRow.oldIndex,
reorderRow.newIndex,
);
_rowChangeReasonNotifier.receive(
RowsChangedReason.reorderSingleRow(
_changedNotifier.receive(
ChangedReason.reorderSingleRow(
reorderRow,
rowInfo,
),
@@ -124,7 +119,7 @@ class RowCache {
for (final rowId in deletedRowIds) {
final deletedRow = _rowList.remove(rowId);
if (deletedRow != null) {
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
_changedNotifier.receive(ChangedReason.delete(deletedRow));
}
}
}
@@ -134,8 +129,7 @@ class RowCache {
final insertedIndex =
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
if (insertedIndex != null) {
_rowChangeReasonNotifier
.receive(RowsChangedReason.insert(insertedIndex));
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
}
}
}
@@ -149,7 +143,7 @@ class RowCache {
fieldId: fieldId,
rowId: updatedRow.rowId,
);
_cellCache.remove(key);
_cellMemCache.remove(key);
}
if (updatedRow.hasRowMeta()) {
updatedList.add(updatedRow.rowMeta);
@@ -160,7 +154,7 @@ class RowCache {
_rowList.updateRows(updatedList, (rowId) => buildGridRow(rowId));
if (updatedIndexs.isNotEmpty) {
_rowChangeReasonNotifier.receive(RowsChangedReason.update(updatedIndexs));
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
}
}
@@ -168,7 +162,7 @@ class RowCache {
for (final rowId in invisibleRows) {
final deletedRow = _rowList.remove(rowId);
if (deletedRow != null) {
_rowChangeReasonNotifier.receive(RowsChangedReason.delete(deletedRow));
_changedNotifier.receive(ChangedReason.delete(deletedRow));
}
}
}
@@ -178,62 +172,45 @@ class RowCache {
final insertedIndex =
_rowList.insert(insertedRow.index, buildGridRow(insertedRow.rowMeta));
if (insertedIndex != null) {
_rowChangeReasonNotifier
.receive(RowsChangedReason.insert(insertedIndex));
_changedNotifier.receive(ChangedReason.insert(insertedIndex));
}
}
}
void onRowsChanged(void Function(RowsChangedReason) onRowChanged) {
_rowChangeReasonNotifier.addListener(() {
onRowChanged(_rowChangeReasonNotifier.reason);
void onRowsChanged(void Function(ChangedReason) onRowChanged) {
_changedNotifier.addListener(() {
onRowChanged(_changedNotifier.reason);
});
}
RowUpdateCallback addListener({
required RowId rowId,
void Function(CellContextByFieldId, RowsChangedReason)? onCellUpdated,
bool Function()? listenWhen,
void Function(CellContextByFieldId, ChangedReason)? onRowChanged,
}) {
listenerHandler() async {
if (listenWhen != null && listenWhen() == false) {
return;
}
notifyUpdate() {
if (onCellUpdated != null) {
final rowInfo = _rowList.get(rowId);
if (rowInfo != null) {
final CellContextByFieldId cellDataMap = _makeGridCells(
rowInfo.rowMeta,
);
onCellUpdated(cellDataMap, _rowChangeReasonNotifier.reason);
}
if (onRowChanged != null) {
final rowInfo = _rowList.get(rowId);
if (rowInfo != null) {
final cellDataMap = _makeCells(rowInfo.rowMeta);
onRowChanged(cellDataMap, _changedNotifier.reason);
}
}
_rowChangeReasonNotifier.reason.whenOrNull(
update: (indexs) {
if (indexs[rowId] != null) notifyUpdate();
},
fieldDidChange: () => notifyUpdate(),
);
}
_rowChangeReasonNotifier.addListener(listenerHandler);
_changedNotifier.addListener(listenerHandler);
return listenerHandler;
}
void removeRowListener(VoidCallback callback) {
_rowChangeReasonNotifier.removeListener(callback);
_changedNotifier.removeListener(callback);
}
CellContextByFieldId loadGridCells(RowMetaPB rowMeta) {
CellContextByFieldId loadCells(RowMetaPB rowMeta) {
final rowInfo = _rowList.get(rowMeta.id);
if (rowInfo == null) {
_loadRow(rowMeta.id);
}
return _makeGridCells(rowMeta);
return _makeCells(rowMeta);
}
Future<void> _loadRow(RowId rowId) async {
@@ -257,18 +234,17 @@ class RowCache {
rowId: rowMetaPB.id,
);
_rowChangeReasonNotifier
.receive(RowsChangedReason.update(updatedIndexs));
_changedNotifier.receive(ChangedReason.update(updatedIndexs));
}
},
(err) => Log.error(err),
);
}
CellContextByFieldId _makeGridCells(RowMetaPB rowMeta) {
CellContextByFieldId _makeCells(RowMetaPB rowMeta) {
// ignore: prefer_collection_literals
final cellContextMap = CellContextByFieldId();
for (final field in _delegate.fields) {
for (final field in _fieldDelegate.fields) {
if (field.visibility) {
cellContextMap[field.id] = DatabaseCellContext(
rowMeta: rowMeta,
@@ -283,7 +259,7 @@ class RowCache {
RowInfo buildGridRow(RowMetaPB rowMetaPB) {
return RowInfo(
viewId: viewId,
fields: _delegate.fields,
fields: _fieldDelegate.fields,
rowId: rowMetaPB.id,
rowMeta: rowMetaPB,
);
@@ -291,11 +267,11 @@ class RowCache {
}
class RowChangesetNotifier extends ChangeNotifier {
RowsChangedReason reason = const InitialListState();
ChangedReason reason = const InitialListState();
RowChangesetNotifier();
void receive(RowsChangedReason newReason) {
void receive(ChangedReason newReason) {
reason = newReason;
reason.map(
insert: (_) => notifyListeners(),
@@ -326,14 +302,14 @@ typedef DeletedIndexs = List<DeletedIndex>;
typedef UpdatedIndexMap = LinkedHashMap<RowId, UpdatedIndex>;
@freezed
class RowsChangedReason with _$RowsChangedReason {
const factory RowsChangedReason.insert(InsertedIndex item) = _Insert;
const factory RowsChangedReason.delete(DeletedIndex item) = _Delete;
const factory RowsChangedReason.update(UpdatedIndexMap indexs) = _Update;
const factory RowsChangedReason.fieldDidChange() = _FieldDidChange;
const factory RowsChangedReason.initial() = InitialListState;
const factory RowsChangedReason.reorderRows() = _ReorderRows;
const factory RowsChangedReason.reorderSingleRow(
class ChangedReason with _$ChangedReason {
const factory ChangedReason.insert(InsertedIndex item) = _Insert;
const factory ChangedReason.delete(DeletedIndex item) = _Delete;
const factory ChangedReason.update(UpdatedIndexMap indexs) = _Update;
const factory ChangedReason.fieldDidChange() = _FieldDidChange;
const factory ChangedReason.initial() = InitialListState;
const factory ChangedReason.reorderRows() = _ReorderRows;
const factory ChangedReason.reorderSingleRow(
ReorderSingleRowPB reorderRow,
RowInfo rowInfo,
) = _ReorderSingleRow;
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../cell/cell_service.dart';
import 'row_cache.dart';
typedef OnRowChanged = void Function(CellContextByFieldId, RowsChangedReason);
typedef OnRowChanged = void Function(CellContextByFieldId, ChangedReason);
class RowController {
final RowMetaPB rowMeta;
@@ -24,13 +24,13 @@ class RowController {
}) : _rowCache = rowCache;
CellContextByFieldId loadData() {
return _rowCache.loadGridCells(rowMeta);
return _rowCache.loadCells(rowMeta);
}
void addListener({OnRowChanged? onRowChanged}) {
final fn = _rowCache.addListener(
rowId: rowMeta.id,
onCellUpdated: onRowChanged,
onRowChanged: onRowChanged,
);
// Add the listener to the list so that we can remove it later.
@@ -0,0 +1,68 @@
import 'dart:typed_data';
import 'package:appflowy/core/notification/grid_notification.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:dartz/dartz.dart';
typedef DidFetchRowCallback = void Function(DidFetchRowPB);
typedef RowMetaCallback = void Function(RowMetaPB);
class RowListener {
final String rowId;
DidFetchRowCallback? _onRowFetchedCallback;
RowMetaCallback? _onMetaChangedCallback;
DatabaseNotificationListener? _listener;
RowListener(this.rowId);
/// OnMetaChanged will be called when the row meta is changed.
/// OnRowFetched will be called when the row is fetched from remote storage
void start({
RowMetaCallback? onMetaChanged,
DidFetchRowCallback? onRowFetched,
}) {
_onMetaChangedCallback = onMetaChanged;
_onRowFetchedCallback = onRowFetched;
_listener = DatabaseNotificationListener(
objectId: rowId,
handler: _handler,
);
}
void _handler(
DatabaseNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateRowMeta:
result.fold(
(payload) {
if (_onMetaChangedCallback != null) {
_onMetaChangedCallback!(RowMetaPB.fromBuffer(payload));
}
},
(error) => Log.error(error),
);
break;
case DatabaseNotification.DidFetchRow:
result.fold(
(payload) {
if (_onRowFetchedCallback != null) {
_onRowFetchedCallback!(DidFetchRowPB.fromBuffer(payload));
}
},
(error) => Log.error(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_onMetaChangedCallback = null;
_onRowFetchedCallback = null;
}
}
@@ -27,20 +27,32 @@ class DatabasePropertyBloc
_startListening();
},
setFieldVisibility: (_SetFieldVisibility value) async {
final fieldBackendSvc =
FieldBackendService(viewId: viewId, fieldId: value.fieldId);
final result =
await fieldBackendSvc.updateField(visibility: value.visibility);
result.fold(
(l) => null,
(err) => Log.error(err),
final fieldBackendSvc = FieldBackendService(
viewId: viewId,
fieldId: value.fieldId,
);
final result = await fieldBackendSvc.updateField(
visibility: value.visibility,
);
result.fold((l) => null, (err) => Log.error(err));
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(fieldContexts: value.fields));
},
moveField: (_MoveField value) {
//
moveField: (_MoveField value) async {
final fieldBackendService = FieldBackendService(
viewId: viewId,
fieldId: value.fieldId,
);
final result = await fieldBackendService.moveField(
value.fromIndex,
value.toIndex,
);
result.fold((l) => null, (r) => Log.error(r));
},
);
},
@@ -76,8 +88,11 @@ class DatabasePropertyEvent with _$DatabasePropertyEvent {
const factory DatabasePropertyEvent.didReceiveFieldUpdate(
List<FieldInfo> fields,
) = _DidReceiveFieldUpdate;
const factory DatabasePropertyEvent.moveField(int fromIndex, int toIndex) =
_MoveField;
const factory DatabasePropertyEvent.moveField({
required String fieldId,
required int fromIndex,
required int toIndex,
}) = _MoveField;
}
@freezed
@@ -46,11 +46,11 @@ class DatabaseViewCache {
required this.viewId,
required FieldController fieldController,
}) : _databaseViewListener = DatabaseViewListener(viewId: viewId) {
final delegate = RowDelegatesImpl(fieldController);
final depsImpl = RowCacheDependenciesImpl(fieldController);
_rowCache = RowCache(
viewId: viewId,
fieldsDelegate: delegate,
cacheDelegate: delegate,
fieldsDelegate: depsImpl,
rowLifeCycle: depsImpl,
);
_databaseViewListener.start(
@@ -6,7 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
@@ -35,6 +35,7 @@ class BoardPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return BoardPage(
key: _makeValueKey(controller),
@@ -96,7 +97,10 @@ class BoardPage extends StatelessWidget {
(_) => BoardContent(
onEditStateChanged: onEditStateChanged,
),
(err) => FlowyErrorPage.message(err.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
(err) => FlowyErrorPage.message(
err.toString(),
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
),
);
},
);
@@ -295,6 +299,7 @@ class _BoardContentState extends State<BoardContent> {
rowCache: rowCache,
cardData: groupData.group.groupId,
groupingFieldId: groupItem.fieldInfo.id,
groupId: groupData.group.groupId,
isEditing: isEditing,
cellBuilder: cellBuilder,
renderHook: renderHook,
@@ -24,7 +24,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
// Getters
String get viewId => databaseController.viewId;
FieldController get fieldController => databaseController.fieldController;
CellCache get cellCache => databaseController.rowCache.cellCache;
CellMemCache get cellCache => databaseController.rowCache.cellCache;
RowCache get rowCache => databaseController.rowCache;
CalendarBloc({required ViewPB view, required this.databaseController})
@@ -21,7 +21,7 @@ class UnscheduleEventsBloc
// Getters
String get viewId => databaseController.viewId;
FieldController get fieldController => databaseController.fieldController;
CellCache get cellCache => databaseController.rowCache.cellCache;
CellMemCache get cellCache => databaseController.rowCache.cellCache;
RowCache get rowCache => databaseController.rowCache;
UnscheduleEventsBloc({
@@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/row/row_cache.dart';
import '../../application/row/row_data_controller.dart';
import '../../application/row/row_controller.dart';
import '../../widgets/row/cell_builder.dart';
import '../../widgets/row/row_detail.dart';
import 'calendar_day.dart';
@@ -27,11 +27,13 @@ class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return CalendarPage(
key: _makeValueKey(controller),
view: view,
databaseController: controller,
shrinkWrap: shrinkWrap,
);
}
@@ -59,9 +61,11 @@ class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
class CalendarPage extends StatefulWidget {
final ViewPB view;
final DatabaseController databaseController;
final bool shrinkWrap;
const CalendarPage({
required this.view,
required this.databaseController,
this.shrinkWrap = false,
super.key,
});
@@ -95,12 +99,8 @@ class _CalendarPageState extends State<CalendarPage> {
Widget build(BuildContext context) {
return CalendarControllerProvider(
controller: _eventController,
child: MultiBlocProvider(
providers: [
BlocProvider<CalendarBloc>.value(
value: _calendarBloc,
)
],
child: BlocProvider<CalendarBloc>.value(
value: _calendarBloc,
child: MultiBlocListener(
listeners: [
BlocListener<CalendarBloc, CalendarState>(
@@ -159,14 +159,10 @@ class _CalendarPageState extends State<CalendarPage> {
],
child: BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
return Column(
children: [
_buildCalendar(
_eventController,
state.settings
.foldLeft(0, (previous, a) => a.firstDayOfWeek),
),
],
return _buildCalendar(
context,
_eventController,
state.settings.foldLeft(0, (previous, a) => a.firstDayOfWeek),
);
},
),
@@ -175,19 +171,26 @@ class _CalendarPageState extends State<CalendarPage> {
);
}
Widget _buildCalendar(EventController eventController, int firstDayOfWeek) {
return Expanded(
child: Padding(
padding: GridSize.contentInsets,
child: MonthView(
Widget _buildCalendar(
BuildContext context,
EventController eventController,
int firstDayOfWeek,
) {
return Padding(
padding: GridSize.contentInsets,
child: LayoutBuilder(
// must specify MonthView width for useAvailableVerticalSpace to work properly
builder: (context, constraints) => MonthView(
key: _calendarState,
controller: _eventController,
cellAspectRatio: .6,
width: constraints.maxWidth,
cellAspectRatio: 0.6,
startDay: _weekdayFromInt(firstDayOfWeek),
borderColor: Theme.of(context).dividerColor,
headerBuilder: _headerNavigatorBuilder,
weekDayBuilder: _headerWeekDayBuilder,
cellBuilder: _calendarDayBuilder,
useAvailableVerticalSpace: widget.shrinkWrap,
),
),
);
@@ -3,7 +3,7 @@ import 'package:flutter/widgets.dart';
class CalendarSize {
static double scale = 1;
static double get scrollBarSize => 12 * scale;
static double get scrollBarSize => 8 * scale;
static double get navigatorButtonWidth => 20 * scale;
static double get navigatorButtonHeight => 25 * scale;
static EdgeInsets get daysOfWeekInsets =>
@@ -153,7 +153,7 @@ class GridEvent with _$GridEvent {
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
const factory GridEvent.didLoadRows(
List<RowInfo> rows,
RowsChangedReason reason,
ChangedReason reason,
) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(
List<FieldInfo> fields,
@@ -179,7 +179,7 @@ class GridState with _$GridState {
required int rowCount,
required GridLoadingState loadingState,
required bool reorderable,
required RowsChangedReason reason,
required ChangedReason reason,
required List<SortInfo> sorts,
required List<FilterInfo> filters,
}) = _GridState;
@@ -15,6 +15,7 @@ class RowActionSheetBloc
RowActionSheetBloc({
required String viewId,
required RowId rowId,
String? groupId,
}) : _rowService = RowBackendService(viewId: viewId),
super(RowActionSheetState.initial(rowId)) {
on<RowActionSheetEvent>(
@@ -25,7 +26,10 @@ class RowActionSheetBloc
logResult(result);
},
duplicateRow: () async {
final result = await _rowService.duplicateRow(rowId: state.rowId);
final result = await _rowService.duplicateRow(
rowId: state.rowId,
groupId: groupId,
);
logResult(result);
},
);
@@ -1,4 +1,6 @@
import 'dart:collection';
import 'package:appflowy/plugins/database_view/application/row/row_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -7,7 +9,7 @@ import 'dart:async';
import '../../../application/cell/cell_service.dart';
import '../../../application/field/field_controller.dart';
import '../../../application/row/row_cache.dart';
import '../../../application/row/row_data_controller.dart';
import '../../../application/row/row_controller.dart';
import '../../../application/row/row_service.dart';
part 'row_bloc.freezed.dart';
@@ -15,6 +17,7 @@ part 'row_bloc.freezed.dart';
class RowBloc extends Bloc<RowEvent, RowState> {
final RowBackendService _rowBackendSvc;
final RowController _dataController;
final RowListener _rowListener;
final String viewId;
final String rowId;
@@ -24,6 +27,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
required RowController dataController,
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
_dataController = dataController,
_rowListener = RowListener(rowId),
super(RowState.initial(dataController.loadData())) {
on<RowEvent>(
(event, emit) async {
@@ -46,6 +50,9 @@ class RowBloc extends Bloc<RowEvent, RowState> {
),
);
},
reloadRow: (DidFetchRowPB row) {
emit(state.copyWith(rowSource: RowSourece.remote(row)));
},
);
},
);
@@ -54,6 +61,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
@override
Future<void> close() async {
_dataController.dispose();
await _rowListener.stop();
return super.close();
}
@@ -65,6 +73,14 @@ class RowBloc extends Bloc<RowEvent, RowState> {
}
},
);
_rowListener.start(
onRowFetched: (fetchRow) {
if (!isClosed) {
add(RowEvent.reloadRow(fetchRow));
}
},
);
}
}
@@ -72,9 +88,10 @@ class RowBloc extends Bloc<RowEvent, RowState> {
class RowEvent with _$RowEvent {
const factory RowEvent.initial() = _InitialRow;
const factory RowEvent.createRow() = _CreateRow;
const factory RowEvent.reloadRow(DidFetchRowPB row) = _ReloadRow;
const factory RowEvent.didReceiveCells(
CellContextByFieldId cellsByFieldId,
RowsChangedReason reason,
ChangedReason reason,
) = _DidReceiveCells;
}
@@ -83,7 +100,8 @@ class RowState with _$RowState {
const factory RowState({
required CellContextByFieldId cellByFieldId,
required UnmodifiableListView<GridCellEquatable> cells,
RowsChangedReason? changeReason,
required RowSourece rowSource,
ChangedReason? changeReason,
}) = _RowState;
factory RowState.initial(
@@ -96,6 +114,7 @@ class RowState with _$RowState {
.map((e) => GridCellEquatable(e.fieldInfo))
.toList(),
),
rowSource: const RowSourece.disk(),
);
}
@@ -112,3 +131,11 @@ class GridCellEquatable extends Equatable {
_fieldContext.width,
];
}
@freezed
class RowSourece with _$RowSourece {
const factory RowSourece.disk() = _Disk;
const factory RowSourece.remote(
DidFetchRowPB row,
) = _Remote;
}
@@ -5,7 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import '../../../application/cell/cell_service.dart';
import '../../../application/field/field_service.dart';
import '../../../application/row/row_data_controller.dart';
import '../../../application/row/row_controller.dart';
part 'row_detail_bloc.freezed.dart';
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
@@ -18,7 +18,7 @@ import 'package:flutter/material.dart';
import 'package:linked_scroll_controller/linked_scroll_controller.dart';
import '../../application/field/field_controller.dart';
import '../../application/row/row_cache.dart';
import '../../application/row/row_data_controller.dart';
import '../../application/row/row_controller.dart';
import '../application/grid_bloc.dart';
import '../../application/database_controller.dart';
import 'grid_scroll.dart';
@@ -50,6 +50,7 @@ class GridPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
BuildContext context,
ViewPB view,
DatabaseController controller,
bool shrinkWrap,
) {
return GridPage(
key: _makeValueKey(controller),
Loaded 100 of 782 files, more files were not shown because too many files have changed in this diff. Show more