Compare commits

...
Author SHA1 Message Date
4bb37c3bcc fix: update cn translate (#2585)
* fix: update cn translate

* chore: update chinese translation

---------

Co-authored-by: yumin2002 <yumin@umich.edu>
Co-authored-by: Yijing Huang <hyj891204@gmail.com>
2023-06-07 15:49:04 +08:00
Lecanora 221311102a chore: spanish and french translations (#2696)
* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations

* inlang: update translations
2023-06-07 15:40:24 +08:00
dependabot[bot] 3639012246 chore: bump openssl in /frontend/appflowy_tauri/src-tauri (#2705)
Bumps [openssl](https://github.com/sfackler/rust-openssl) from 0.10.45 to 0.10.54.
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.45...openssl-v0.10.54)

---
updated-dependencies:
- dependency-name: openssl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-05 14:49:11 +08:00
Lucas.Xu 71d1189048 chore: bump version 0.1.6 (#2665) 2023-05-31 13:12:49 +08:00
Nathan.fooo 757009a97d chore: revert reorder rows in grid (#2533) (#2669)
This reverts commit f8f0599462.
2023-05-31 11:21:33 +08:00
Lucas.Xuandnathan e8665dc76c feat: support exporting document data to afdoc format (#2610)
* feat: support exporting document data to afdoc format

* feat: export database

* fix: resolve comment issues

* fix: add error tips when exporting files failed

---------

Co-authored-by: nathan <nathan@appflowy.io>
2023-05-30 17:55:38 +08:00
Daniyar Nurmukhamet f022f9aa06 feature: changed url field ux to be directly editable (#2523)
* feature: changed url field ux to be directly editable

* feature: added link svg icon
2023-05-30 11:04:10 +08:00
Alex Wallen 32650b7941 Revert "feat: support regenerating ai results #2012 (#2528)" (#2642)
This reverts commit 1f1a97b21b.
2023-05-28 11:56:38 -10:00
Alex Wallen 5a7339b092 [Fix] Integration test flakes. (#2635)
* feat: add tearDown and tearDownAll to reset workspaces

* fix: register tearDown and tearDownAll in empty_document_test

* feat: register tearDown and tearDownAll in board_test

* feat: register tearDown and tearDownAll in open_ai
2023-05-28 10:23:20 -10:00
Nathan.fooo 7ca028942c chore: implement export handler (#2625)
* chore: implement export handler

* chore: fix export
2023-05-27 21:21:34 +08:00
Alex Wallen 6fbd88fe76 Revert "Revert "feat: support regenerating ai results #2012 (#2528)" (#2622)" (#2636)
This reverts commit 2b5ecf5609.
2023-05-26 14:00:20 -10:00
Alex Wallen 2b5ecf5609 Revert "feat: support regenerating ai results #2012 (#2528)" (#2622)
This reverts commit 1f1a97b21b.
2023-05-26 13:59:21 -10:00
GitStartandgitstart 18672b197c fix: Update Indonesian language from Bahasa to Bahasa Indonesia (#2555)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2023-05-19 20:00:25 +08:00
GitStartandgitstart 63a267ca6d fix: Text in Show menu tooltip does not get translated (#2563)
Co-authored-by: gitstart <gitstart@users.noreply.github.com>
2023-05-19 19:42:06 +08:00
1f1a97b21b feat: support regenerating ai results #2012 (#2528)
* feat: added emoji and network image support

* fix: code cleanup and improvements

* feat: rewrite AI response #2012

* fix: code refactor, rewrite in smart edit

* chore: delete the redundant file

---------

Co-authored-by: ahmeduzair890 <ahmeduzair12123@gmail.com>
Co-authored-by: rizwan3395 <rizwan.ios33gmail.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-05-19 19:26:39 +08:00
Nils Jacobsen ca1ce8e19e chore: update inlang.config.js (#2542) 2023-05-19 19:26:04 +08:00
Mathias Mogensen f8f0599462 feat: reorder rows in grid (#2533)
* feat: reorder rows in grid

Allows reordering of rows in a Grid where no current filter and sorting is applied

Closes: #1123

* fix: resolve review comments
2023-05-16 21:20:05 +08:00
Daniyar NurmukhametandLucas.Xu a4845e668d feature: added name icon (#2529)
* feature: added name icon

* feature: added generating color in the defined HSL ranges

* refactor: removed trailing commas for readibility

* fix: fixed trimming name and separated color picker into util file

* fix: changed to using where instead of trim

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

* feat: simplify the color generator

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
2023-05-15 19:11:43 +08:00
Daniyar Nurmukhamet dd5bc7808c feature: added making text longer (#2532)
* feature: added making text longer

* fix: corrected I10n
2023-05-15 17:01:37 +08:00
Richard Shiue a993a2a181 fix: remove icon button not working in popover (#2518) 2023-05-15 10:59:34 +08:00
Richard Shiue 99ad9dcaff fix: don't show whitespace when no options are selected (#2493) 2023-05-15 10:56:38 +08:00
Annie d44de62b90 chore: Update README.md
Add a screenshot of the calendar view
2023-05-12 17:12:00 +08:00
Lucas.Xu 33b5837f04 chore: update changelog.md (#2510) 2023-05-11 19:26:26 +08:00
Lucas.Xu 54f757d9b2 bump version 0.1.5 (#2506)
* chore: bump version 0.1.5

* fix: could not trigger slash menu after inserting an emoji

* Revert "feat: add hover effect on an event card (#2487)"

This reverts commit f0a4b4b77d.

* feat: add hover effect on an event card

* fix: #2469 duplicated cover
2023-05-11 19:04:36 +08:00
Nathan.fooo cf27409c8d fix: number sort (#2507) 2023-05-11 18:35:36 +08:00
Lucas.Xu bd5250ae2f chore: update editor v0.1.12 and format the readme (#2489) 2023-05-10 15:01:15 +08:00
Yijing Huang f0a4b4b77d feat: add hover effect on an event card (#2487)
* chore: delete unncessary openCard method in RowCardContainer

* chore: delete unnessary code and add comment
2023-05-10 10:19:52 +08:00
Richard Shiue 151ee89855 fix: show delete icon for document icon properly (#2475) 2023-05-09 22:35:33 +08:00
Richard Shiue 2b7282195b feat: improve sidebar item dragged appearance (#2471) 2023-05-09 22:33:42 +08:00
Richard Shiue 4a1714b537 feat: select which properties to show in calendar (#2482) 2023-05-09 22:26:25 +08:00
Richard Shiue 6731037a8c fix: wrong day of week (#2468) 2023-05-08 09:51:20 +08:00
Annie 06f056aa4b chore: Update README.md
Update product screenshots
2023-05-07 15:47:45 +08:00
Lucas.Xu a2e9d63f63 chore: bump version 0.1.4 (#2455) 2023-05-04 20:05:23 +08:00
Richard Shiue 5d0a349236 fix: default include time (#2444)
* fix: default include time

* chore: clarify logic and add comments
2023-05-04 12:41:48 +08:00
Richard ShiueandNathan.fooo db8d030a85 feat: show unscheduled events in calendar toolbar (#2411)
* refactor: use same show row detail function

* fix: adjust popover offset

* feat: show unscheduled events in toolbar

* chore: apply suggestions from Xazin

* refactor: refactor list item into separate widget

---------

Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
2023-05-04 11:05:17 +08:00
66 changed files with 1694 additions and 695 deletions

No files matched your search

+1 -1
View File
@@ -138,7 +138,7 @@ jobs:
job:
- {
target: x86_64-apple-darwin,
os: macos-10.15,
os: macos-11,
extra-build-args: "",
}
steps:
+27
View File
@@ -1,5 +1,32 @@
# Release Notes
## Version 0.1.6 - 30/05/2023
### New features
- Support exporting your document and database data.
## Version 0.1.5 - 11/05/2023
### Bug Fixes
- Fix: calendar dates don't match with weekdays.
- Fix: sort numbers in Grid.
## Version 0.1.4 - 04/05/2023
### New features
- Use AppFlowy’s calendar views to plan and manage tasks and deadlines.
- Writing can be improved with the help of OpenAI.
## Version 0.1.3 - 24/04/2023
### New features
- Launch the official Dark Mode.
- Customize the font color and highlight color by setting a hex color value and an opacity level.
### Bug Fixes
- Fix: the slash menu can be triggered by all other keyboards than English.
- Fix: convert the single asterisk to italic text and the double asterisks to bold text.
## Version 0.1.2 - 03/28/2023
### Bug Fixes
+5 -3
View File
@@ -23,9 +23,11 @@ You are in charge of your data and customizations.
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
</p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664610-fc209a97-815e-4716-af07-d94a859d1907.png" alt="AppFlowy Docs & Notes & Wikis" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664628-5def2450-914a-4b2d-b907-92b7476b9863.png" alt="AppFlowy Databases for Tasks and Projects" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664642-22e26c1b-5eae-4635-9aa6-b12ecf1c3c46.png" alt="AppFlowy Kanban Board for To-Dos" width="1000px" /></p>
<p align="center"><img src="https://github.com/AppFlowy-IO/AppFlowy/assets/12026239/6be93d2b-a5c5-48a9-b7cf-c599d5f5140c" alt="AppFlowy Calendars for Plan and Manage Content" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664657-dc5291f3-67b0-4a43-a818-640e92735deb.png" alt="AppFlowy OpenAI GPT Writers" width="1000px" /></p>
## User Installation
+1 -1
View File
@@ -23,7 +23,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.1.3"
CURRENT_APP_VERSION = "0.1.6"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -0,0 +1,4 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7 8.91521C7.21574 9.22582 7.49099 9.48283 7.80707 9.66881C8.12315 9.85479 8.47268 9.96538 8.83194 9.99309C9.1912 10.0208 9.5518 9.96497 9.88926 9.8294C10.2267 9.69383 10.5332 9.48169 10.7878 9.20736L12.2949 7.58431C12.7525 7.07413 13.0056 6.39083 12.9999 5.68156C12.9942 4.9723 12.73 4.29384 12.2643 3.7923C11.7986 3.29075 11.1686 3.00627 10.51 3.0001C9.85142 2.99394 9.21693 3.26659 8.7432 3.75935L7.87913 4.68448" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 7.08479C8.78426 6.77418 8.50901 6.51717 8.19293 6.33119C7.87685 6.14521 7.52732 6.03462 7.16806 6.00691C6.8088 5.9792 6.4482 6.03503 6.11074 6.1706C5.77327 6.30617 5.46683 6.51831 5.21218 6.79264L3.7051 8.41569C3.24755 8.92587 2.99437 9.60918 3.00009 10.3184C3.00582 11.0277 3.26998 11.7062 3.73569 12.2077C4.2014 12.7092 4.8314 12.9937 5.48999 12.9999C6.14858 13.0061 6.78307 12.7334 7.2568 12.2407L8.11584 11.3155" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -419,4 +419,4 @@
"layoutDateField": "تقويم التخطيط بواسطة"
}
}
}
}
@@ -45,7 +45,8 @@
"medium": "medium",
"large": "large",
"fontSize": "Font Size",
"import": "Import"
"import": "Import",
"moreOptions": "More options"
},
"disclosureAction": {
"rename": "Rename",
@@ -183,6 +184,7 @@
},
"files": {
"defaultLocation": "Where your data is stored now",
"exportData": "Export your data",
"doubleTapToCopy": "Double tap to copy the path",
"restoreLocation": "Restore to AppFlowy default path",
"customizeLocation": "Open another folder",
@@ -201,7 +203,9 @@
"create": "Create",
"folderPath": "Path to store your folder",
"locationCannotBeEmpty": "Path cannot be empty",
"pathCopiedSnackbar": "File storage path copied to clipboard!"
"pathCopiedSnackbar": "File storage path copied to clipboard!",
"exportFileSuccess": "Export file successfully!",
"exportFileFail": "Export file failed!"
},
"user": {
"name": "Name",
@@ -363,7 +367,8 @@
"smartEditFixSpelling": "Fix spelling",
"warning": "⚠️ AI responses can be inaccurate or misleading.",
"smartEditSummarize": "Summarize",
"smartEditImproveWriting": "Improve Writing",
"smartEditImproveWriting": "Improve writing",
"smartEditMakeLonger": "Make longer",
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
"smartEditDisabled": "Connect OpenAI in Settings",
@@ -418,7 +423,9 @@
"showWeekNumbers": "Show week numbers",
"showWeekends": "Show weekends",
"firstDayOfWeek": "Start week on",
"layoutDateField": "Layout calendar by"
"layoutDateField": "Layout calendar by",
"noDateTitle": "No Date",
"emptyNoDate": "No unscheduled events"
}
}
}
@@ -1,6 +1,6 @@
{
"appName": "AppFlowy",
"defaultUsername": "Mi",
"defaultUsername": "Yo",
"welcomeText": "Bienvenido a @:appName",
"githubStarText": "Favorito en GitHub",
"subscribeNewsletterText": "Suscribir al boletín",
@@ -25,7 +25,7 @@
"forgotPassword": "¿Olvidó su contraseña?",
"emailHint": "Correo",
"passwordHint": "Contraseña",
"dontHaveAnAccount": "¿No posee credenciales?",
"dontHaveAnAccount": "¿No tienes una cuenta?",
"repeatPasswordEmptyError": "La contraseña no puede estar en blanco",
"unmatchedPasswordError": "Las contraseñas no coinciden"
},
@@ -37,7 +37,7 @@
"shareAction": {
"buttonText": "Compartir",
"workInProgress": "Próximamente",
"markdown": "Marcador",
"markdown": "Markdown",
"copyLink": "Copiar enlace"
},
"disclosureAction": {
@@ -68,13 +68,13 @@
"help": "Ayuda y Soporte",
"debug": {
"name": "Información de depuración",
"success": "¡Información copiada!",
"fail": "No fue posible copiar la información"
"success": "¡Información de depuración copiada al portapapeles!",
"fail": "No fue posible copiar la información de depuración "
}
},
"menuAppHeader": {
"addPageTooltip": "Inserta una página",
"defaultNewPageName": "Sin Título",
"defaultNewPageName": "Sin título",
"renameDialog": "Renombrar"
},
"toolbar": {
@@ -146,10 +146,10 @@
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"label": "Tema",
"light": "Modo Claro",
"dark": "Modo Oscuro",
"system": "Adapt to System"
"system": "Adaptar al sistema"
}
}
},
@@ -227,5 +227,163 @@
"column": {
"create_new_card": "Nuevo"
}
}
},
"settings.files.customizeLocation": "Abrir otra carpeta",
"moreAction.small": "pequeño",
"moreAction.medium": "mediano",
"moreAction.large": "grande",
"moreAction.fontSize": "Tamaño de letra",
"moreAction.import": "Importar",
"questionBubble.shortcuts": "Atajos",
"questionBubble.markdown": "Markdown",
"tooltip.addNewRow": "Añadir nueva línea",
"tooltip.openAsPage": "Abrir como página",
"tooltip.openMenu": "Haz click para abrir el menú",
"tooltip.viewDataBase": "Ver base de datos",
"button.Done": "Hecho",
"button.generate": "Generar",
"button.tryAgain": "Prueba otra vez",
"button.discard": "Descartar",
"button.replace": "Reemplazar",
"button.insertBelow": "Insertar debajo",
"settings.menu.user": "Usuario",
"settings.menu.files": "Archivos",
"settings.appearance.theme": "Tema",
"settings.files.restartApp": "Por favor reinicie la app para que los cambios tengan efecto.",
"settings.files.exportDatabase": "Exportar base de datos",
"settings.files.createNewFolder": "Crear nueva carpeta",
"settings.files.createNewFolderDesc": "Dinos donde quieres guardar tus datos",
"settings.files.open": "Abrir",
"settings.files.openFolder": "Abrir una carpeta existente",
"settings.user.icon": "Icono",
"settings.user.selectAnIcon": "Selecciona un icono",
"grid.settings.addFilter": "Añadir filtro",
"grid.settings.deleteFilter": "Eliminar filtro",
"grid.textFilter.contains": "Contiene",
"grid.textFilter.doesNotContain": "No contiene",
"document.plugins.autoGeneratorLearnMore": "Saber más",
"settings.files.location": "Creando una nueva carpeta",
"settings.user.name": "Nombre",
"grid.settings.sort": "Ordenar",
"grid.settings.filterBy": "Filtrar por...",
"grid.field.dateFormatDayMonthYear": "Día/Mes/Año",
"grid.sort.ascending": "Ascendente",
"grid.sort.descending": "Descendente",
"document.plugins.cover.colors": "Colores",
"document.plugins.cover.images": "Imágenes",
"document.plugins.cover.addIcon": "Añadir icono",
"calendar.menuName": "Calendario",
"calendar.defaultNewCalendarTitle": "Sin título",
"calendar.navigation.today": "Hoy",
"calendar.navigation.previousMonth": "Mes anterior",
"calendar.navigation.nextMonth": "Próximo mes",
"moreAction.moreOptions": "Más opciones",
"calendar.settings.noDateTitle": "Sin fecha",
"grid.row.newRow": "Nueva línea",
"grid.multiSelectOptionFilter.contains": "Contiene",
"grid.multiSelectOptionFilter.doesNotContain": "No contiene",
"grid.singleSelectOptionFilter.is": "Es",
"grid.singleSelectOptionFilter.isNot": "No es",
"grid.settings.group": "Grupo",
"settings.files.create": "Crear",
"settings.files.browser": "Navegar",
"settings.files.locationDesc": "Elija un nombre para su carpeta de datos de AppFlowy",
"settings.files.folderHintText": "nombre de la carpeta",
"document.plugins.smartEditImproveWriting": "Mejorar la escritura",
"settings.files.doubleTapToCopy": "Toca dos veces para copiar la ruta",
"button.keep": "Mantener",
"grid.textFilter.endsWith": "Termina con",
"grid.textFilter.startWith": "Comienza con",
"grid.textFilter.isNot": "No es",
"grid.textFilter.is": "Es",
"grid.textFilter.choicechipPrefix.startWith": "Comienza con",
"grid.textFilter.choicechipPrefix.endWith": "Termina con",
"grid.checklistFilter.isComplete": "está completo",
"grid.checklistFilter.isIncomplted": "está incompleto",
"grid.singleSelectOptionFilter.isEmpty": "Está vacio",
"grid.singleSelectOptionFilter.isNotEmpty": "No está vacío",
"grid.multiSelectOptionFilter.isEmpty": "Está vacío",
"grid.multiSelectOptionFilter.isNotEmpty": "No está vacío",
"grid.checkboxFilter.choicechipPrefix.is": "es",
"grid.field.newProperty": "Nueva propiedad",
"grid.field.deleteFieldPromptMessage": "¿Estás seguro? Esta propiedad será eliminada",
"grid.checklist.panelTitle": "Agregar un elemento",
"grid.referencedGridPrefix": "Vista de",
"document.slashMenu.grid.createANewGrid": "Crear una nueva cuadrícula",
"document.plugins.referencedGrid": "Cuadrícula referenciada",
"document.plugins.referencedBoard": "Tablero referenciado",
"document.plugins.autoGeneratorGenerate": "Generar",
"document.plugins.autoGeneratorHintText": "Pregúntale a OpenAI...",
"document.plugins.openAI": "OpenAI",
"document.plugins.warning": "⚠️ Las respuestas de la IA pueden ser inexactas o engañosas.",
"document.plugins.smartEditSummarize": "Resumir",
"document.plugins.smartEditCouldNotFetchKey": "No se pudo obtener la clave de OpenAI",
"document.plugins.smartEditCouldNotFetchResult": "No se pudo obtener el resultado de OpenAI",
"document.plugins.cover.addLocalImage": "Agregar imagen local",
"document.plugins.cover.add": "Agregar",
"document.plugins.cover.back": "Atrás",
"document.plugins.cover.removeIcon": "Eliminar icono",
"document.plugins.cover.or": "O",
"document.plugins.cover.imageSavingFailed": "Error al guardar la imagen",
"board.menuName": "Tablero",
"board.referencedBoardPrefix": "Vista de",
"calendar.navigation.jumpToday": "Saltar a hoy",
"calendar.settings.showWeekNumbers": "Mostrar números de semana",
"calendar.settings.showWeekends": "Mostrar fines de semana",
"calendar.settings.firstDayOfWeek": "Empezar la semana en",
"settings.files.exportData": "Exporta tus datos",
"grid.row.action": "Acción",
"document.plugins.smartEditMakeLonger": "Hacer más largo",
"calendar.settings.emptyNoDate": "Sin eventos programados",
"document.plugins.mathEquation.addMathEquation": "Agregar fórmula matemática",
"document.plugins.mathEquation.editMathEquation": "Editar fórmula matemática",
"document.plugins.cover.alertDialogConfirmation": "¿Estás seguro de querer continuar?",
"document.plugins.cover.couldNotFetchImage": "No se pudo obtener la imagen",
"document.plugins.cover.pasteImageUrl": "Pegar URL de imagen",
"document.plugins.cover.saveToGallery": "Guardar en la galería",
"document.plugins.cover.enterImageUrl": "Introducir URL de la imagen",
"document.plugins.cover.invalidImageUrl": "URL de imagen no válida",
"document.plugins.cover.clearAll": "Limpiar todo",
"document.plugins.discardResponse": "¿Quieres descartar las respuestas de la IA?",
"document.plugins.smartEditDisabled": "Conectar a OpenAI en Ajustes",
"document.plugins.autoGeneratorCantGetOpenAIKey": "No se pudo obtener la clave de OpenAI",
"document.slashMenu.grid.selectAGridToLinkTo": "Seleccione una cuadrícula para vincular",
"document.slashMenu.board.createANewBoard": "Crear un nuevo tablero",
"grid.sort.addSort": "Agregar clasificación",
"grid.sort.deleteSort": "Eliminar clasificación",
"grid.textFilter.choicechipPrefix.isNotEmpty": "no está vacío",
"grid.textFilter.choicechipPrefix.isEmpty": "está vacío",
"grid.textFilter.isNotEmpty": "No está vacío",
"grid.textFilter.choicechipPrefix.isNot": "No",
"grid.textFilter.isEmpty": "Está vacío",
"grid.settings.typeAValue": "Escribe un valor...",
"settings.user.pleaseInputYourOpenAIKey": "por favor ingrese su clave de OpenAI",
"settings.files.pathCopiedSnackbar": "¡La ruta de almacenamiento de archivos se copió al portapapeles!",
"settings.files.locationCannotBeEmpty": "La ruta no puede estar vacía",
"settings.files.folderPath": "Ruta para almacenar tu carpeta",
"settings.files.selectFiles": "Seleccione los archivos que necesiten ser exportados",
"grid.checkboxFilter.isChecked": "Comprobado",
"grid.checkboxFilter.isUnchecked": "No comprobado",
"tooltip.referencePage": "Este {nombre} está referenciado",
"grid.settings.layout": "Disposición",
"settings.files.exportFileSuccess": "¡El archivo ha sido exportado con éxito!",
"settings.files.exportFileFail": "¡Error al exportar el archivo!",
"grid.row.count": "Recuento",
"settings.files.restoreLocation": "Restaurar a la ruta predeterminada de AppFlowy",
"document.slashMenu.board.selectABoardToLinkTo": "Seleccione un tablero para vincular",
"document.plugins.autoGeneratorTitleName": "OpenAI: Pídele a la IA que escriba algo...",
"document.plugins.cover.failedToAddImageToGallery": "No se pudo agregar la imagen a la galería",
"button.esc": "ESC",
"settings.files.openFolderDesc": "Lee y escribe en tu carpeta de AppFlowy existente",
"grid.field.checklistFieldName": "Lista de verificación",
"document.plugins.autoGeneratorMenuItemName": "Escritor de OpenAI",
"document.plugins.smartEditFixSpelling": "Corregir ortografía",
"document.plugins.cover.coverRemoveAlert": "Se quitará de la portada después de ser eliminado.",
"document.plugins.cover.pickFromFiles": "Seleccionar desde archivos",
"calendar.settings.layoutDateField": "Disposición del calendario por",
"document.plugins.cover.addCover": "Agregar portada",
"settings.files.defaultLocation": "Donde se almacenan sus datos ahora",
"document.plugins.smartEdit": "Asistentes de IA",
"document.plugins.cover.changeCover": "Cambiar portada",
"document.plugins.cover.abstract": "Resumen"
}
@@ -156,7 +156,7 @@
"label": "Theme Mode",
"light": "Mode clair",
"dark": "Mode sombre",
"system": "Adapt to System"
"system": "Adapter au système"
}
}
},
@@ -235,5 +235,87 @@
"column": {
"create_new_card": "Nouveau"
}
}
},
"settings.files.exportDatabase": "Exporter la base de données",
"settings.files.open": "Ouvrir",
"settings.files.openFolder": "Ouvrir un dossier existant",
"settings.files.location": "Création d'un nouveau dossier",
"settings.files.create": "Créer",
"settings.user.icon": "Icône",
"settings.user.selectAnIcon": "Sélectionnez une icône",
"grid.settings.addFilter": "Ajouter un filtre",
"grid.settings.deleteFilter": "Supprimer filtre",
"grid.field.dateFormatDayMonthYear": "Jour/Mois/Année",
"grid.sort.ascending": "Ascendant",
"grid.sort.descending": "Descendant",
"grid.checklist.panelTitle": "Ajouter un élément",
"document.slashMenu.board.createANewBoard": "Créer un nouveau tableau",
"document.slashMenu.grid.createANewGrid": "Créer une nouvelle grille",
"document.plugins.autoGeneratorLearnMore": "Savoir plus",
"document.plugins.autoGeneratorGenerate": "Générer",
"document.plugins.cover.colors": "Couleurs",
"document.plugins.cover.images": "Images",
"document.plugins.cover.clearAll": "Tout effacer",
"document.plugins.cover.addCover": "Ajouter une couverture",
"document.plugins.cover.abstract": "Résumé",
"document.plugins.cover.changeCover": "Changer couverture",
"document.plugins.cover.removeIcon": "Supprimer l'icône",
"document.plugins.cover.or": "OU",
"document.plugins.cover.addIcon": "Ajouter une icône",
"board.menuName": "Tableau",
"board.referencedBoardPrefix": "Vue de",
"calendar.menuName": "Calendrier",
"calendar.defaultNewCalendarTitle": "Sans titre",
"calendar.navigation.today": "Aujourd'hui",
"calendar.navigation.jumpToday": "Aller à aujourd'hui",
"calendar.navigation.previousMonth": "Mois précédent",
"calendar.navigation.nextMonth": "Mois prochain",
"calendar.settings.showWeekNumbers": "Afficher les numéros de semaine",
"calendar.settings.showWeekends": "Afficher les week-ends",
"calendar.settings.firstDayOfWeek": "Commencer la semaine le",
"settings.files.exportData": "Exportez vos données",
"moreAction.moreOptions": "Plus d'options",
"document.plugins.cover.coverRemoveAlert": "Il sera retiré de la couverture après sa suppression.",
"settings.files.folderHintText": "nom du dossier",
"settings.appearance.theme": "Thème",
"button.replace": "Remplacer",
"button.esc": "ESC",
"moreAction.small": "petit",
"moreAction.medium": "moyen",
"moreAction.large": "grand",
"moreAction.import": "Importer",
"questionBubble.shortcuts": "Raccourcis",
"toolbar.color": "Couleur",
"questionBubble.markdown": "Markdown",
"tooltip.viewDataBase": "Voir la base de données",
"button.generate": "Générer",
"grid.settings.sort": "Trier",
"grid.settings.filterBy": "Filtrer par...",
"grid.textFilter.contains": "Contient",
"grid.textFilter.doesNotContain": "Ne contient pas",
"grid.textFilter.endsWith": "Se termine par",
"grid.textFilter.is": "Est",
"grid.textFilter.startWith": "Commence avec",
"grid.textFilter.isNot": "N'est pas",
"grid.textFilter.isEmpty": "Est vide",
"grid.textFilter.isNotEmpty": "N'est pas vide",
"grid.textFilter.choicechipPrefix.endWith": "Se termine par",
"grid.textFilter.choicechipPrefix.isNotEmpty": "n'est pas vide",
"grid.textFilter.choicechipPrefix.isEmpty": "est vide",
"grid.singleSelectOptionFilter.is": "Est",
"grid.singleSelectOptionFilter.isNot": "N'est pas",
"grid.singleSelectOptionFilter.isEmpty": "Est vide",
"grid.singleSelectOptionFilter.isNotEmpty": "N'est pas vide",
"grid.multiSelectOptionFilter.contains": "Contient",
"grid.multiSelectOptionFilter.doesNotContain": "Ne contient pas",
"grid.multiSelectOptionFilter.isEmpty": "Est vide",
"grid.multiSelectOptionFilter.isNotEmpty": "N'est pas vide",
"grid.sort.addSort": "Ajouter un tri",
"grid.referencedGridPrefix": "Vue de",
"grid.sort.deleteSort": "Supprimer le tri",
"grid.checkboxFilter.choicechipPrefix.is": "est",
"grid.checklistFilter.isComplete": "est complet",
"grid.checklistFilter.isIncomplted": "est incomplet",
"grid.textFilter.choicechipPrefix.startWith": "Commence avec",
"settings.user.name": "Prénom"
}
@@ -2,7 +2,7 @@
"appName": "Appflowy",
"defaultUsername": "我",
"welcomeText": "欢迎使用 @:appName",
"githubStarText": "Star on GitHub",
"githubStarText": "在GitHub上收藏",
"subscribeNewsletterText": "消息订阅",
"letsGoButtonText": "开始",
"title": "标题",
@@ -40,6 +40,14 @@
"markdown": "Markdown",
"copyLink": "复制链接"
},
"moreAction": {
"small": "小",
"medium": "中",
"large": "大",
"fontSize": "字号",
"import": "导入",
"moreOptions": "更多选项"
},
"disclosureAction": {
"rename": "重命名",
"delete": "删除",
@@ -53,8 +61,8 @@
"deleteAll": "全部删除",
"pageHeader": {
"fileName": "文件名",
"lastModified": "最近修改",
"created": "创建"
"lastModified": "最近修改时间",
"created": "创建时间"
}
},
"deletePagePrompt": {
@@ -64,16 +72,18 @@
},
"dialogCreatePageNameHint": "页面名称",
"questionBubble": {
"whatsNew": "新功能?",
"whatsNew": "查看最新功能",
"help": "帮助 & 支持",
"shortcuts":"快捷键",
"markdown":"markdown指南",
"debug": {
"name": "调试信息",
"success": "将调试信息复制到剪贴板!",
"fail": "无法将调试信息复制到剪贴板"
"success": "成功复制调试信息至剪贴板",
"fail": "复制调试信息至剪贴板失败!"
}
},
"menuAppHeader": {
"addPageTooltip": "在其中快速添加页面",
"addPageTooltip": "新增子页面",
"defaultNewPageName": "未命名页面",
"renameDialog": "重命名"
},
@@ -90,14 +100,18 @@
"inlineCode": "内联代码",
"quote": "块引用",
"header": "标题",
"highlight": "高亮"
"highlight": "高亮",
"color": "颜色"
},
"tooltip": {
"lightMode": "切换到亮色模式",
"darkMode": "切换到暗色模式",
"openAsPage": "作为页面打开",
"addNewRow": "增加一行",
"openMenu": "点击打开菜单"
"openMenu": "点击打开菜单",
"dragRow": "长按排序",
"viewDataBase": "查看参考页",
"referencePage": "{name}来自参考页"
},
"sideBar": {
"openSidebar": "打开侧边栏",
@@ -117,11 +131,19 @@
},
"button": {
"OK": "确认",
"Done": "完成",
"Cancel": "取消",
"signIn": "登录",
"signOut": "登出",
"complete": "完成",
"save": "保存"
"save": "保存",
"generate": "生成",
"esc": "离开",
"keep": "保存",
"tryAgain": "重试",
"discard": "放弃变更",
"replace": "替换",
"insertBelow": "下方插入"
},
"label": {
"welcome": "欢迎!",
@@ -149,23 +171,99 @@
"appearance": "外观",
"language": "语言",
"user": "用户",
"files": "文件",
"open": "打开设置"
},
"appearance": {
"themeMode": {
"label": "Theme Mode",
"label": "主题模式",
"light": "日间模式",
"dark": "夜间模式",
"system": "Adapt to System"
}
"system": "自适应"
},
"theme": "主题"
},
"files": {
"defaultLocation": "Appflowy数据当前存储路径",
"doubleTapToCopy": "双击复制路径",
"restoreLocation": "恢复默认路径",
"customizeLocation": "设置新存储路径",
"restartApp": "请重启APP后使更改生效",
"exportDatabase": "导出数据库",
"selectFiles": "选择导出文件",
"createNewFolder": "新建文件夹",
"createNewFolderDesc": "选择你想储存资料的位置",
"open": "打开",
"openFolder": "打开已经存在的文件夹",
"openFolderDesc": "读写已经存在的AppFlowy文件夹",
"folderHintText": "文件夹名称",
"location": "新建文件夹",
"locationDesc": "命名AppFlowy文件夹",
"browser": "浏览",
"create": "新建",
"folderPath": "文件夹储存路径",
"locationCannotBeEmpty": "路径不能为空",
"pathCopiedSnackbar": "路径已复制至剪切板"
},
"user": {
"name": "用户名",
"icon": "图标",
"selectAnIcon": "选择图标",
"pleaseInputYourOpenAIKey": "请输入 OpenAI 密钥"
}
},
"grid": {
"settings": {
"filter": "过滤器",
"sortBy": "排序",
"sort": "排序",
"sortBy": "排序方式",
"Properties": "属性",
"group": "组"
"group": "组",
"addFilter": "增加过滤器",
"deleteFilter": "删除过滤器",
"filterBy": "过滤方式",
"typeAValue": "输入值",
"layout": "布局"
},
"textFilter": {
"contains": "包括",
"doesNotContain": "不包括",
"endsWith": "后缀",
"startWith": "前缀",
"is": "是",
"isNot": "不是",
"isEmpty": "为空",
"isNotEmpty": "不为空",
"choicechipPrefix": {
"isNot": "不是",
"startWith": "前缀",
"endWith": "后缀",
"isEmpty": "为空",
"isNotEmpty": "不为空"
}
},
"checkboxFilter": {
"isChecked": "勾选",
"isUnchecked": "未勾选",
"choicechipPrefix": {
"is": "是"
}
},
"checklistFilter": {
"isComplete": "已完成",
"isIncomplted": "未完成"
},
"singleSelectOptionFilter": {
"is": "是",
"isNot": "不是",
"isEmpty": "为空",
"isNotEmpty": "不为空"
},
"multiSelectOptionFilter": {
"contains": "包括",
"doesNotContain": "不包括",
"isEmpty": "为空",
"isNotEmpty": "不为空"
},
"field": {
"hide": "隐藏",
@@ -180,6 +278,7 @@
"singleSelectFieldName": "单项选择器",
"multiSelectFieldName": "多项选择器",
"urlFieldName": "链接",
"checklistFieldName": "清单",
"numberFormat": "数字格式",
"dateFormat": "日期格式",
"includeTime": "包含时间",
@@ -187,6 +286,7 @@
"dateFormatISO": "年-月-日",
"dateFormatLocal": "月/日/年",
"dateFormatUS": "年/月/日",
"dateFormatDayMonthYear": "日/月/年",
"timeFormat": "时间格式",
"invalidTimeFormat": "时间格式错误",
"timeFormatTwelveHour": "12小时制",
@@ -196,15 +296,22 @@
"addOption": "添加标签",
"editProperty": "编辑列属性",
"newProperty": "增加一列",
"deleteFieldPromptMessage": "确定要删除这个属性吗? "
"deleteFieldPromptMessage": "确认要删除这个属性吗? "
},
"sort": {
"ascending": "升序排列",
"descending": "降序排列",
"deleteSort": "删除排序",
"addSort": "新增排序"
},
"row": {
"duplicate": "复制",
"delete": "删除",
"textPlaceholder": "空",
"textPlaceholder": "无",
"copyProperty": "复制列",
"count": "数量",
"newRow": "添加一行"
"newRow": "添加一行",
"action": "操作"
},
"selectOption": {
"create": "新建",
@@ -222,19 +329,101 @@
"panelTitle": "选择或新建一个标签",
"searchOption": "搜索标签"
},
"menuName": "网格"
"checklist": {
"panelTitle": "增加项"
},
"menuName": "网格",
"referencedGridPrefix": "视图: "
},
"document": {
"menuName": "文档",
"date": {
"timeHintTextInTwelveHour": "01:00 PM",
"timeHintTextInTwentyFourHour": "13:00"
},
"slashMenu": {
"board": {
"selectABoardToLinkTo": "选择链接看板",
"createANewBoard": "新建看板"
},
"grid": {
"selectAGridToLinkTo": "选择链接网格",
"createANewGrid": "新建网格"
}
},
"plugins": {
"referencedBoard": "引用看板",
"referencedGrid": "引用网格",
"autoGeneratorMenuItemName": "OpenAI 写作助手",
"autoGeneratorTitleName": "OpenAI: 请为AI输入指令...",
"autoGeneratorLearnMore": "了解更多",
"autoGeneratorGenerate": "生成",
"autoGeneratorHintText": "询问 OpenAI ...",
"autoGeneratorCantGetOpenAIKey": "无法取得 OpenAI k密钥",
"smartEdit": "AI 助手",
"openAI": "OpenAI",
"smartEditFixSpelling": "拼写修正",
"warning": "⚠️ 请留意不准确或错误的 AI 回答",
"smartEditSummarize": "总结",
"smartEditImproveWriting": "修改",
"smartEditMakeLonger": "扩写",
"smartEditCouldNotFetchResult": "无法从 OpenAI 获取结果",
"smartEditCouldNotFetchKey": "获取 OpenAI 密钥失败",
"smartEditDisabled": "请在“设置”中添加您的OpenAI 密钥后使用",
"discardResponse": "是否弃用 AI 回答?",
"cover": {
"changeCover": "更换封面",
"colors": "颜色",
"images": "图片",
"clearAll": "清除全部",
"abstract": "摘要",
"addCover": "新增封面",
"addLocalImage": "添加本地图片",
"invalidImageUrl": "无效图片链接",
"failedToAddImageToGallery": "添加图片至图库失败",
"enterImageUrl": "输入图片链接",
"add": "添加",
"back": "返回",
"saveToGallery": "储存到图库",
"removeIcon": "删除图标",
"pasteImageUrl": "复制图片链接",
"or": "或",
"pickFromFiles": "从文件中选择",
"couldNotFetchImage": "无法获取图片",
"imageSavingFailed": "图片保存失败",
"addIcon": "增加图标",
"coverRemoveAlert": "它将被从封面删除",
"alertDialogConfirmation": "确认继续"
},
"mathEquation": {
"addMathEquation": "新建数学公式",
"editMathEquation": "编辑数学公式"
}
}
},
"board": {
"column": {
"create_new_card": "新建"
},
"menuName": "看板"
"menuName": "看板",
"referencedBoardPrefix": "视图: "
},
"calendar": {
"menuName": "日历",
"defaultNewCalendarTitle": "未命名",
"navigation": {
"today": "今天",
"jumpToday": "跳转至今天",
"previousMonth": "上个月",
"nextMonth": "下个月"
},
"settings": {
"showWeekNumbers": "显示周数",
"showWeekends": "显示周末",
"firstDayOfWeek": "设置每周起始日",
"layoutDateField": "按该日期属性布局",
"noDateTitle": "未安排事件",
"emptyNoDate": "所有事件已安排"
}
}
}
}
@@ -34,6 +34,8 @@ void main() {
group('board', () {
setUpAll(() async => await service.setUpAll());
setUp(() async => await service.setUp());
tearDown(() async => await service.tearDown());
tearDownAll(() async => await service.tearDownAll());
testWidgets(
'integration test unzips the proper workspace and loads it correctly.',
@@ -30,6 +30,8 @@ void main() {
group('Tests on a workspace with only an empty document', () {
setUpAll(() async => await service.setUpAll());
setUp(() async => await service.setUp());
tearDown(() async => await service.tearDown());
tearDownAll(() async => await service.tearDownAll());
testWidgets('/board shortcut creates a new board and view of the board',
(tester) async {
@@ -69,6 +71,7 @@ void main() {
],
tester: tester,
);
await tester.pumpAndSettle();
// Checks whether new board is referenced and properly on the page.
expect(find.byType(BuiltInPageWidget), findsOneWidget);
@@ -113,7 +116,12 @@ void main() {
expect(find.byType(SelectionMenuItemWidget), findsAtLeastNWidgets(2));
// Finalizes the slash command that creates the board.
await simulateKeyDownEvent(LogicalKeyboardKey.enter);
await FlowyTestKeyboard.simulateKeyDownEvent(
[
LogicalKeyboardKey.enter,
],
tester: tester,
);
await tester.pumpAndSettle();
// Checks whether new board is referenced and properly on the page.
@@ -16,6 +16,8 @@ void main() {
group('integration tests for open-ai smart menu', () {
setUpAll(() async => await service.setUpAll());
setUp(() async => await service.setUp());
tearDown(() async => await service.tearDown());
tearDownAll(() async => await service.tearDownAll());
testWidgets('testing selection on open-ai smart menu replace', (tester) async {
final appFlowyEditor = await setUpOpenAITesting(tester);
@@ -39,6 +39,13 @@ enum TestWorkspace {
return root;
}
Future<void> clean() async {
final Directory workspaceRoot = await root;
if (await workspaceRoot.exists()) {
workspaceRoot.delete(recursive: true);
}
}
String get _asset => 'assets/test/workspaces/$_name.zip';
}
@@ -67,4 +74,12 @@ class TestWorkspaceService {
await TestWorkspace._parent.then((value) => value.path),
);
}
Future<void> tearDown() async {
await workspace.clean();
}
Future<void> tearDownAll() async {
await SharedPreferences.getInstance().then((value) => value.remove(kSettingsLocationDefaultLocation));
}
}
@@ -45,12 +45,7 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
Future<Option<FlowyError>> save(DateCellData data) {
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
// This is a bit of a hack. This converts the data.date which is in
// UTC to Local but actually changes the timestamp instead of just
// changing the isUtc flag
final dateTime = DateTime(data.date.year, data.date.month, data.date.day);
final date = (dateTime.millisecondsSinceEpoch ~/ 1000).toString();
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date;
payload.isUtc = data.date.isUtc;
payload.includeTime = data.includeTime;
@@ -51,5 +51,6 @@ class CalendarSettingEvent with _$CalendarSettingEvent {
}
enum CalendarSettingAction {
properties,
layout,
}
@@ -1,24 +1,23 @@
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/widgets/card/card.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../grid/presentation/layout/sizes.dart';
import '../../widgets/row/cells/select_option_cell/extension.dart';
import '../application/calendar_bloc.dart';
import 'calendar_page.dart';
class CalendarDayCard extends StatelessWidget {
final String viewId;
@@ -102,7 +101,7 @@ class CalendarDayCard extends StatelessWidget {
);
}
GestureDetector _buildCard(BuildContext context, CalendarDayEvent event) {
Widget _buildCard(BuildContext context, CalendarDayEvent event) {
final styles = <FieldType, CardCellStyle>{
FieldType.Number: NumberCardCellStyle(10),
FieldType.URL: URLCardCellStyle(10),
@@ -162,6 +161,9 @@ class CalendarDayCard extends StatelessWidget {
});
renderHook.addSelectOptionHook((selectedOptions, cardData, _) {
if (selectedOptions.isEmpty) {
return const SizedBox.shrink();
}
final children = selectedOptions.map(
(option) {
return SelectOptionTag.fromOption(
@@ -193,7 +195,12 @@ class CalendarDayCard extends StatelessWidget {
cardData: event.dateFieldId,
isEditing: false,
cellBuilder: cellBuilder,
openCard: (context) => _showRowDetailPage(event, context),
openCard: (context) => showEventDetails(
context: context,
event: event,
viewId: viewId,
rowCache: _rowCache,
),
styleConfiguration: const RowCardStyleConfiguration(
showAccessory: false,
cellPadding: EdgeInsets.zero,
@@ -203,44 +210,24 @@ class CalendarDayCard extends StatelessWidget {
onEndEditing: () {},
);
return GestureDetector(
onTap: () => _showRowDetailPage(event, context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(
color: Theme.of(context).dividerColor,
width: 1.5,
),
),
borderRadius: Corners.s6Border,
),
child: card,
),
return FlowyHover(
style: HoverStyle(
hoverColor: Theme.of(context).colorScheme.tertiaryContainer,
foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
),
);
}
void _showRowDetailPage(CalendarDayEvent event, BuildContext context) {
final dataController = RowController(
rowId: event.eventId,
viewId: viewId,
rowCache: _rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: _rowCache.cellCache,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(
color: Theme.of(context).dividerColor,
width: 1.5,
),
),
dataController: dataController,
);
},
borderRadius: Corners.s6Border,
),
child: card,
),
);
}
@@ -9,6 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
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 '../../widgets/row/cell_builder.dart';
import '../../widgets/row/row_detail.dart';
@@ -76,7 +77,12 @@ class _CalendarPageState extends State<CalendarPage> {
listenWhen: (p, c) => p.editEvent != c.editEvent,
listener: (context, state) {
if (state.editEvent != null) {
_showEditEventPage(state.editEvent!.event!, context);
showEventDetails(
context: context,
event: state.editEvent!.event!,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
}
},
),
@@ -165,8 +171,9 @@ class _CalendarPageState extends State<CalendarPage> {
}
Widget _headerWeekDayBuilder(day) {
// incoming day starts from Monday, the symbols start from Sunday
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
final weekDayString = symbols.WEEKDAYS[day];
final weekDayString = symbols.WEEKDAYS[(day + 1) % 7];
return Center(
child: Padding(
padding: CalendarSize.daysOfWeekInsets,
@@ -210,27 +217,32 @@ class _CalendarPageState extends State<CalendarPage> {
}
WeekDays _weekdayFromInt(int dayOfWeek) {
// MonthView places the first day of week on the second column for some reason.
return WeekDays.values[(dayOfWeek + 1) % 7];
}
void _showEditEventPage(CalendarDayEvent event, BuildContext context) {
final dataController = RowController(
rowId: event.eventId,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: _calendarBloc.rowCache.cellCache,
),
dataController: dataController,
);
},
);
// dayOfWeek starts from Sunday, WeekDays starts from Monday
return WeekDays.values[(dayOfWeek - 1) % 7];
}
}
void showEventDetails({
required BuildContext context,
required CalendarDayEvent event,
required String viewId,
required RowCache rowCache,
}) {
final dataController = RowController(
rowId: event.eventId,
viewId: viewId,
rowCache: rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: rowCache.cellCache,
),
dataController: dataController,
);
},
);
}
@@ -217,6 +217,7 @@ class LayoutDateField extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) {
return BlocProvider(
create: (context) => getIt<DatabasePropertyBloc>(
@@ -237,9 +238,9 @@ class LayoutDateField extends StatelessWidget {
onUpdated(fieldInfo.id);
popoverMutex.close();
},
leftIcon: svgWidget('grid/field/date'),
leftIcon: const FlowySvg(name: 'grid/field/date'),
rightIcon: fieldInfo.id == fieldId
? svgWidget('grid/checkmark')
? const FlowySvg(name: 'grid/checkmark')
: null,
),
);
@@ -333,12 +334,13 @@ class FirstDayOfWeek extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) {
final symbols =
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
// starts from sunday
final items = symbols.WEEKDAYS.asMap().entries.map((entry) {
final index = (entry.key - 1) % 7;
final index = entry.key;
final string = entry.value;
return SizedBox(
height: GridSize.popoverItemHeight,
@@ -348,8 +350,9 @@ class FirstDayOfWeek extends StatelessWidget {
onUpdated(index);
popoverMutex.close();
},
rightIcon:
firstDayOfWeek == index ? svgWidget('grid/checkmark') : null,
rightIcon: firstDayOfWeek == index
? const FlowySvg(name: 'grid/checkmark')
: null,
),
);
}).toList();
@@ -2,8 +2,10 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@@ -38,6 +40,11 @@ class CalendarSetting extends StatelessWidget {
final CalendarSettingAction? action =
state.selectedAction.foldLeft(null, (previous, action) => action);
switch (action) {
case CalendarSettingAction.properties:
return GridPropertyList(
viewId: settingContext.viewId,
fieldController: settingContext.fieldController,
);
case CalendarSettingAction.layout:
return CalendarLayoutSetting(
onUpdated: onUpdated,
@@ -78,9 +85,16 @@ class AllCalendarSettings extends StatelessWidget {
}
Widget _settingItem(BuildContext context, CalendarSettingAction action) {
Widget? icon;
if (action.iconName() != null) {
icon = FlowySvg(
name: action.iconName()!,
);
}
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
leftIcon: icon,
text: FlowyText.medium(action.title()),
onTap: () {
context
@@ -93,8 +107,19 @@ class AllCalendarSettings extends StatelessWidget {
}
extension _SettingExtension on CalendarSettingAction {
String? iconName() {
switch (this) {
case CalendarSettingAction.properties:
return 'grid/setting/properties';
case CalendarSettingAction.layout:
return null;
}
}
String title() {
switch (this) {
case CalendarSettingAction.properties:
return LocaleKeys.grid_settings_Properties.tr();
case CalendarSettingAction.layout:
return LocaleKeys.grid_settings_layout.tr();
}
@@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -19,7 +21,8 @@ class CalendarToolbar extends StatelessWidget {
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
children: const [
_UnscheduleEventsButton(),
_SettingButton(),
],
),
@@ -28,25 +31,22 @@ class CalendarToolbar extends StatelessWidget {
}
class _SettingButton extends StatefulWidget {
const _SettingButton({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SettingButtonState();
}
class _SettingButtonState extends State<_SettingButton> {
late PopoverController popoverController;
@override
void initState() {
popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.bottomWithRightAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(300, 400)),
margin: EdgeInsets.zero,
child: FlowyTextButton(
@@ -54,7 +54,6 @@ class _SettingButtonState extends State<_SettingButton> {
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: () => popoverController.show(),
),
popupBuilder: (BuildContext popoverContext) {
final bloc = context.watch<CalendarBloc>();
@@ -81,3 +80,100 @@ class _SettingButtonState extends State<_SettingButton> {
);
}
}
class _UnscheduleEventsButton extends StatefulWidget {
const _UnscheduleEventsButton({Key? key}) : super(key: key);
@override
State<_UnscheduleEventsButton> createState() =>
_UnscheduleEventsButtonState();
}
class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
late final PopoverController _controller;
@override
void initState() {
super.initState();
_controller = PopoverController();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
final unscheduledEvents = state.allEvents
.where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
.toList();
final viewId = context.read<CalendarBloc>().viewId;
final rowCache = context.read<CalendarBloc>().rowCache;
return AppFlowyPopover(
direction: PopoverDirection.bottomWithCenterAligned,
controller: _controller,
offset: const Offset(0, 8),
child: FlowyTextButton(
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})",
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
),
popupBuilder: (context) {
if (unscheduledEvents.isEmpty) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: Center(
child: FlowyText.medium(
LocaleKeys.calendar_settings_emptyNoDate.tr(),
color: Theme.of(context).hintColor,
),
),
);
}
return ListView.separated(
itemBuilder: (context, index) => _UnscheduledEventItem(
event: unscheduledEvents[index],
onPressed: () {
showEventDetails(
context: context,
event: unscheduledEvents[index].event!,
viewId: viewId,
rowCache: rowCache,
);
_controller.close();
},
),
itemCount: unscheduledEvents.length,
separatorBuilder: (context, index) =>
VSpace(GridSize.typeOptionSeparatorHeight),
shrinkWrap: true,
);
},
);
},
);
}
}
class _UnscheduledEventItem extends StatelessWidget {
final CalendarEventData<CalendarDayEvent> event;
final VoidCallback onPressed;
const _UnscheduledEventItem({
required this.event,
required this.onPressed,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyTextButton(
event.title,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: onPressed,
),
);
}
}
@@ -43,6 +43,7 @@ class RowCardContainer extends StatelessWidget {
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => openCard(context),
child: Padding(
padding: const EdgeInsets.all(8),
@@ -84,15 +84,23 @@ class DateCellCalendarBloc
bool? includeTime,
}) {
final DateCellData newDateData = state.dateCellData.fold(
() => DateCellData(
date: date ?? DateTime.now(),
time: time,
includeTime: includeTime ?? false,
),
() {
DateTime newDate = DateTime.now();
if (date != null) {
newDate = _utcToLocalAddTime(date);
}
return DateCellData(
date: newDate,
time: time,
includeTime: includeTime ?? false,
);
},
(dateData) {
var newDateData = dateData;
if (date != null && !isSameDay(newDateData.date, date)) {
newDateData = newDateData.copyWith(date: date);
newDateData = newDateData.copyWith(
date: _utcToLocalAddTime(date),
);
}
if (newDateData.time != time) {
@@ -151,6 +159,21 @@ class DateCellCalendarBloc
);
}
DateTime _utcToLocalAddTime(DateTime date) {
final now = DateTime.now();
// the incoming date is Utc. this trick converts it into Local
// and add the current time, though
// the time may be overwritten by explicitly provided time string
return DateTime(
date.year,
date.month,
date.day,
now.hour,
now.minute,
now.second,
);
}
String timeFormatPrompt(FlowyError error) {
String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}.";
switch (state.dateTypeOptionPB.timeFormat) {
@@ -19,6 +19,8 @@ import 'url_cell_bloc.dart';
class GridURLCellStyle extends GridCellStyle {
String? placeholder;
TextStyle? textStyle;
bool? autofocus;
List<GridURLCellAccessoryType> accessoryTypes;
@@ -29,8 +31,8 @@ class GridURLCellStyle extends GridCellStyle {
}
enum GridURLCellAccessoryType {
edit,
copyURL,
visitURL,
}
class GridURLCell extends GridCellWidget {
@@ -56,15 +58,14 @@ class GridURLCell extends GridCellWidget {
GridCellAccessoryBuildContext buildContext,
) {
switch (ty) {
case GridURLCellAccessoryType.edit:
case GridURLCellAccessoryType.visitURL:
final cellContext = cellControllerBuilder.build() as URLCellController;
return GridCellAccessoryBuilder(
builder: (Key key) => _EditURLAccessory(
builder: (Key key) => _VisitURLAccessory(
key: key,
anchorContext: buildContext.anchorContext,
cellControllerBuilder: cellControllerBuilder,
cellContext: cellContext,
),
);
case GridURLCellAccessoryType.copyURL:
final cellContext = cellControllerBuilder.build() as URLCellController;
return GridCellAccessoryBuilder(
@@ -89,11 +90,11 @@ class GridURLCell extends GridCellWidget {
);
}
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.visitURL
if (accessories.isEmpty) {
accessories.add(
accessoryFromType(
GridURLCellAccessoryType.edit,
GridURLCellAccessoryType.visitURL,
buildContext,
),
);
@@ -106,6 +107,8 @@ class GridURLCell extends GridCellWidget {
class _GridURLCellState extends GridCellState<GridURLCell> {
final _popoverController = PopoverController();
late URLCellBloc _cellBloc;
late TextEditingController _controller;
late FocusNode _focusNode;
@override
void initState() {
@@ -113,6 +116,8 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
widget.cellControllerBuilder.build() as URLCellController;
_cellBloc = URLCellBloc(cellController: cellController);
_cellBloc.add(const URLCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);
_focusNode = FocusNode();
super.initState();
}
@@ -122,60 +127,61 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
value: _cellBloc,
child: BlocBuilder<URLCellBloc, URLCellState>(
builder: (context, state) {
final richText = Padding(
padding: GridSize.cellContentInsets,
child: FlowyText.medium(
state.content,
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
final urlEditor = Padding(
padding: EdgeInsets.only(
left: GridSize.cellContentInsets.left,
right: GridSize.cellContentInsets.right,
),
);
return AppFlowyPopover(
margin: EdgeInsets.zero,
controller: _popoverController,
constraints: BoxConstraints.loose(const Size(300, 160)),
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerFlags.none,
offset: const Offset(0, 8),
child: SizedBox.expand(
child: GestureDetector(
child: Align(alignment: Alignment.centerLeft, child: richText),
onTap: () async {
final url = context.read<URLCellBloc>().state.url;
await _openUrlOrEdit(url);
},
child: TextField(
controller: _controller,
focusNode: _focusNode,
maxLines: 1,
style: (widget.cellStyle?.textStyle ??
Theme.of(context).textTheme.bodyMedium)
?.copyWith(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
autofocus: false,
onEditingComplete: focusChanged,
onSubmitted: (value) => focusChanged(isUrlSubmitted: true),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(
top: GridSize.cellContentInsets.top,
bottom: GridSize.cellContentInsets.bottom,
),
border: InputBorder.none,
hintText: widget.cellStyle?.placeholder,
isDense: true,
),
),
popupBuilder: (BuildContext popoverContext) {
return URLEditorPopover(
cellController:
widget.cellControllerBuilder.build() as URLCellController,
onExit: () => _popoverController.close(),
);
},
onClose: () {
widget.onCellEditing.value = false;
},
);
return urlEditor;
},
),
);
}
void focusChanged({
bool isUrlSubmitted = false,
}) {
if (mounted) {
if (_cellBloc.isClosed == false &&
_controller.text != _cellBloc.state.content) {
_cellBloc.add(URLCellEvent.updateURL(_controller.text));
}
if (isUrlSubmitted) {
_focusNode.unfocus();
}
}
}
@override
Future<void> dispose() async {
_cellBloc.close();
super.dispose();
}
Future<void> _openUrlOrEdit(String url) async {
final uri = Uri.parse(url);
if (url.isNotEmpty && await canLaunchUrl(uri)) {
await launchUrl(uri);
}
}
@override
void requestBeginFocus() {
widget.onCellEditing.value = true;
@@ -269,3 +275,36 @@ class _CopyURLAccessoryState extends State<_CopyURLAccessory>
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
}
}
class _VisitURLAccessory extends StatefulWidget {
final URLCellController cellContext;
const _VisitURLAccessory({required this.cellContext, Key? key})
: super(key: key);
@override
State<StatefulWidget> createState() => _VisitURLAccessoryState();
}
class _VisitURLAccessoryState extends State<_VisitURLAccessory>
with GridCellAccessoryState {
@override
Widget build(BuildContext context) {
return svgWidget(
"editor/link",
color: AFThemeExtension.of(context).textColor,
);
}
@override
void onTap() {
var content =
widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
if (!content.contains('http://')) {
content = 'http://$content';
}
final uri = Uri.parse(content);
if (content.isNotEmpty) {
canLaunchUrl(uri).then((value) => launchUrl(uri));
}
}
}
@@ -361,8 +361,8 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
return GridURLCellStyle(
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
accessoryTypes: [
GridURLCellAccessoryType.edit,
GridURLCellAccessoryType.copyURL,
GridURLCellAccessoryType.visitURL,
],
);
}
@@ -226,6 +226,9 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
if (temporaryNodeTypes.contains(node.type)) {
transaction.deleteNode(node);
}
if (kCoverType == node.type && !node.path.equals([0])) {
transaction.deleteNode(node);
}
}
if (transaction.operations.isNotEmpty) {
await editorState.apply(transaction, withUpdateCursor: false);
@@ -1,5 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/more/font_size_switcher.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -14,6 +16,7 @@ class DocumentMoreButton extends StatelessWidget {
return PopupMenuButton<int>(
color: Theme.of(context).colorScheme.surfaceVariant,
offset: const Offset(0, 30),
tooltip: LocaleKeys.moreAction_moreOptions.tr(),
itemBuilder: (context) {
return [
PopupMenuItem(
@@ -1,11 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
class EmojiPopover extends StatefulWidget {
@@ -33,57 +32,53 @@ class _EmojiPopoverState extends State<EmojiPopover> {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(15),
child: EmojiPicker(
onEmojiSelected: (category, emoji) {
widget.onEmojiChanged(emoji);
},
customWidget: (Config config, EmojiViewState state) {
return Stack(
alignment: Alignment.topRight,
children: [
Container(
padding: EdgeInsets.only(top: widget.showRemoveButton ? 25 : 0),
child: DefaultEmojiPickerView(config, state),
),
_buildDeleteButtonIfNeed(),
],
);
},
config: Config(
columns: 8,
emojiSizeMax: 28,
bgColor: Colors.transparent,
iconColor: Theme.of(context).iconTheme.color!,
iconColorSelected: Theme.of(context).colorScheme.onSurface,
selectedHoverColor: Theme.of(context).colorScheme.secondary,
progressIndicatorColor: Theme.of(context).iconTheme.color!,
buttonMode: ButtonMode.CUPERTINO,
initCategory: Category.RECENT,
),
),
);
}
Widget _buildDeleteButtonIfNeed() {
if (!widget.showRemoveButton) {
return const SizedBox();
}
return FlowyButton(
onTap: () => widget.removeIcon(),
useIntrinsicWidth: true,
text: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
child: Column(
children: [
const FlowySvg(name: 'editor/delete'),
const SizedBox(
width: 5,
),
FlowyText(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
if (widget.showRemoveButton)
Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Align(
alignment: Alignment.centerRight,
child: DeleteButton(onPressed: widget.removeIcon),
),
),
Expanded(
child: EmojiPicker(
onEmojiSelected: (category, emoji) {
widget.onEmojiChanged(emoji);
},
config: Config(
columns: 8,
emojiSizeMax: 28,
bgColor: Colors.transparent,
iconColor: Theme.of(context).iconTheme.color!,
iconColorSelected: Theme.of(context).colorScheme.onSurface,
selectedHoverColor: Theme.of(context).colorScheme.secondary,
progressIndicatorColor: Theme.of(context).iconTheme.color!,
buttonMode: ButtonMode.CUPERTINO,
initCategory: Category.RECENT,
),
),
),
],
),
);
}
}
class DeleteButton extends StatelessWidget {
final VoidCallback onPressed;
const DeleteButton({required this.onPressed, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlowyButton(
onTap: onPressed,
useIntrinsicWidth: true,
text: FlowyText(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
),
leftIcon: const FlowySvg(name: 'editor/delete'),
);
}
}
@@ -29,29 +29,32 @@ void _showEmojiSelectionMenu(
menuService.dismiss();
_emojiSelectionMenu?.remove();
_emojiSelectionMenu = OverlayEntry(builder: (context) {
return Positioned(
top: alignment == Alignment.bottomLeft ? offset.dy : null,
bottom: alignment == Alignment.topLeft ? offset.dy : null,
left: offset.dx,
child: Material(
child: EmojiSelectionMenu(
editorState: editorState,
onSubmitted: (text) {
// insert emoji
editorState.insertEmoji(text);
},
onExit: () {
_dismissEmojiSelectionMenu();
//close emoji panel
},
_emojiSelectionMenu = OverlayEntry(
builder: (context) {
return Positioned(
top: alignment == Alignment.bottomLeft ? offset.dy : null,
bottom: alignment == Alignment.topLeft ? offset.dy : null,
left: offset.dx,
child: Material(
child: EmojiSelectionMenu(
editorState: editorState,
onSubmitted: (text) {
// insert emoji
editorState.insertEmoji(text);
},
onExit: () {
_dismissEmojiSelectionMenu();
//close emoji panel
},
),
),
),
);
},);
);
},
);
Overlay.of(context).insert(_emojiSelectionMenu!);
_editorState = editorState;
editorState.service.selectionService.currentSelection
.addListener(_dismissEmojiSelectionMenu);
}
@@ -62,6 +65,7 @@ void _dismissEmojiSelectionMenu() {
_editorState?.service.selectionService.currentSelection
.removeListener(_dismissEmojiSelectionMenu);
_editorState?.service.keyboardService?.enable();
_editorState = null;
}
@@ -6,7 +6,8 @@ import 'package:easy_localization/easy_localization.dart';
enum SmartEditAction {
summarize,
fixSpelling,
improveWriting;
improveWriting,
makeItLonger;
String get toInstruction {
switch (this) {
@@ -16,6 +17,8 @@ enum SmartEditAction {
return 'Correct this to standard English:';
case SmartEditAction.improveWriting:
return 'Rewrite this in your own words:';
case SmartEditAction.makeItLonger:
return 'Make this text longer:';
}
}
@@ -27,6 +30,8 @@ enum SmartEditAction {
return 'Correct this to standard English:\n\n$input';
case SmartEditAction.improveWriting:
return 'Rewrite this:\n\n$input';
case SmartEditAction.makeItLonger:
return 'Make this text longer:\n\n$input';
}
}
@@ -38,6 +43,8 @@ enum SmartEditAction {
return SmartEditAction.fixSpelling;
case 2:
return SmartEditAction.improveWriting;
case 3:
return SmartEditAction.makeItLonger;
}
return SmartEditAction.fixSpelling;
}
@@ -50,6 +57,8 @@ enum SmartEditAction {
return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
case SmartEditAction.improveWriting:
return LocaleKeys.document_plugins_smartEditImproveWriting.tr();
case SmartEditAction.makeItLonger:
return LocaleKeys.document_plugins_smartEditMakeLonger.tr();
}
}
}
@@ -179,4 +179,4 @@ void _resolveGridDeps(GetIt getIt) {
(viewId, cache) =>
DatabasePropertyBloc(viewId: viewId, fieldController: cache),
);
}
}
@@ -0,0 +1,11 @@
import 'dart:ui';
class ColorGenerator {
Color generateColorFromString(String string) {
final hash = string.hashCode;
int r = (hash & 0xFF0000) >> 16;
int g = (hash & 0x00FF00) >> 8;
int b = hash & 0x0000FF;
return Color.fromRGBO(r, g, b, 0.5);
}
}
@@ -1,4 +1,5 @@
import 'package:appflowy_backend/protobuf/flowy-folder/app.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SettingsFileExportState {
@@ -8,6 +9,20 @@ class SettingsFileExportState {
initialize();
}
List<ViewPB> get selectedViews {
final selectedViews = <ViewPB>[];
for (var i = 0; i < apps.length; i++) {
if (selectedApps[i]) {
for (var j = 0; j < apps[i].belongings.items.length; j++) {
if (selectedItems[i][j]) {
selectedViews.add(apps[i].belongings.items[j]);
}
}
}
}
return selectedViews;
}
List<AppPB> apps;
List<bool> expanded = [];
List<bool> selectedApps = [];
@@ -61,6 +61,10 @@ class ViewSection extends StatelessWidget {
.add(ViewSectionEvent.moveView(oldIndex, index));
},
ignorePrimaryScrollController: true,
buildDraggableFeedback: (context, constraints, child) => ConstrainedBox(
constraints: constraints,
child: Material(color: Colors.transparent, child: child),
),
children: children,
);
}
@@ -149,6 +149,8 @@ class HomeMenu extends StatelessWidget {
),
);
},
proxyDecorator: (child, index, animation) =>
Material(color: Colors.transparent, child: child),
);
},
),
@@ -1,4 +1,5 @@
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/color_generator/color_generator.dart';
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
@@ -45,8 +46,31 @@ class MenuUser extends StatelessWidget {
String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
if (iconUrl.isEmpty) {
iconUrl = defaultUserAvatar;
final String name = context.read<MenuUserBloc>().state.userProfile.name;
final Color color = ColorGenerator().generateColorFromString(name);
const initialsCount = 2;
// Taking the first letters of the name components and limiting to 2 elements
final nameInitials = name
.split(' ')
.where((element) => element.isNotEmpty)
.take(initialsCount)
.map((element) => element[0].toUpperCase())
.join('');
return Container(
width: 28,
height: 28,
alignment: Alignment.center,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
child: FlowyText.semibold(
nameInitials,
color: Colors.white,
fontSize: nameInitials.length == initialsCount ? 12 : 14,
),
);
}
return SizedBox(
width: 25,
height: 25,
@@ -0,0 +1,59 @@
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_exporter_widget.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import '../../../../generated/locale_keys.g.dart';
class SettingsExportFileWidget extends StatefulWidget {
const SettingsExportFileWidget({
super.key,
});
@override
State<SettingsExportFileWidget> createState() =>
SettingsExportFileWidgetState();
}
@visibleForTesting
class SettingsExportFileWidgetState extends State<SettingsExportFileWidget> {
@override
Widget build(BuildContext context) {
return ListTile(
title: FlowyText.medium(
LocaleKeys.settings_files_exportData.tr(),
overflow: TextOverflow.ellipsis,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
Tooltip(
message: LocaleKeys.settings_files_open.tr(),
child: FlowyIconButton(
height: 40,
width: 40,
icon: const Icon(Icons.folder_open_outlined),
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
onPressed: () async {
await showDialog(
context: context,
builder: (context) {
return const FlowyDialog(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 16,
vertical: 20,
),
child: FileExporterWidget(),
),
);
},
);
},
),
),
],
),
);
}
}
@@ -12,8 +12,8 @@ import '../../../../startup/launch_configuration.dart';
import '../../../../startup/startup.dart';
import '../../../../startup/tasks/prelude.dart';
class SettingsFileLocationCustomzier extends StatefulWidget {
const SettingsFileLocationCustomzier({
class SettingsFileLocationCustomizer extends StatefulWidget {
const SettingsFileLocationCustomizer({
super.key,
required this.cubit,
});
@@ -21,13 +21,13 @@ class SettingsFileLocationCustomzier extends StatefulWidget {
final SettingsLocationCubit cubit;
@override
State<SettingsFileLocationCustomzier> createState() =>
SettingsFileLocationCustomzierState();
State<SettingsFileLocationCustomizer> createState() =>
SettingsFileLocationCustomizerState();
}
@visibleForTesting
class SettingsFileLocationCustomzierState
extends State<SettingsFileLocationCustomzier> {
class SettingsFileLocationCustomizerState
extends State<SettingsFileLocationCustomizer> {
@override
Widget build(BuildContext context) {
return BlocProvider<SettingsLocationCubit>.value(
@@ -1,15 +1,22 @@
import 'dart:io';
import 'package:appflowy/plugins/document/application/prelude.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/workspace/application/settings/settings_file_exporter_cubit.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder;
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:path/path.dart' as p;
import 'package:tuple/tuple.dart';
import '../../../../generated/locale_keys.g.dart';
class FileExporterWidget extends StatefulWidget {
@@ -22,24 +29,44 @@ class FileExporterWidget extends StatefulWidget {
class _FileExporterWidgetState extends State<FileExporterWidget> {
// Map<String, List<String>> _selectedPages = {};
SettingsFileExporterCubit? cubit;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.medium(
LocaleKeys.settings_files_selectFiles.tr(),
fontSize: 16.0,
),
const VSpace(8),
Expanded(child: _buildFileSelector(context)),
const VSpace(8),
_buildButtons(context)
],
return FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>(
future: FolderEventReadCurrentWorkspace().send(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
final workspaces = snapshot.data?.getLeftOrNull<WorkspaceSettingPB>();
if (workspaces != null) {
final apps = workspaces.workspace.apps.items;
cubit ??= SettingsFileExporterCubit(apps: apps);
return BlocProvider<SettingsFileExporterCubit>.value(
value: cubit!,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
FlowyText.medium(
LocaleKeys.settings_files_selectFiles.tr(),
fontSize: 16.0,
),
const VSpace(8),
const Expanded(child: _ExpandedList()),
const VSpace(8),
_buildButtons()
],
),
);
}
}
return const CircularProgressIndicator();
},
);
}
Row _buildButtons(BuildContext context) {
Widget _buildButtons() {
return Row(
children: [
const Spacer(),
@@ -55,8 +82,28 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
onPressed: () async {
await getIt<FilePickerService>()
.getDirectoryPath()
.then((exportPath) {
Navigator.of(context).pop();
.then((exportPath) async {
if (exportPath != null && cubit != null) {
final views = cubit!.state.selectedViews;
final result =
await _AppFlowyFileExporter.exportToPath(exportPath, views);
if (result.item1) {
// success
_showToast(LocaleKeys.settings_files_exportFileSuccess.tr());
} else {
_showToast(
LocaleKeys.settings_files_exportFileFail.tr() +
result.item2.join('\n'),
);
}
} else {
_showToast(LocaleKeys.settings_files_exportFileFail.tr());
}
if (mounted) {
Navigator.of(context).popUntil(
(router) => router.settings.name == '/',
);
}
});
},
),
@@ -64,24 +111,14 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
);
}
FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>
_buildFileSelector(BuildContext context) {
return FutureBuilder<dartz.Either<WorkspaceSettingPB, FlowyError>>(
future: FolderEventReadCurrentWorkspace().send(),
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
final workspaces = snapshot.data?.getLeftOrNull<WorkspaceSettingPB>();
if (workspaces != null) {
final apps = workspaces.workspace.apps.items;
return BlocProvider<SettingsFileExporterCubit>(
create: (_) => SettingsFileExporterCubit(apps: apps),
child: const _ExpandedList(),
);
}
}
return const CircularProgressIndicator();
},
void _showToast(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: FlowyText(
message,
color: Theme.of(context).colorScheme.onSurface,
),
),
);
}
}
@@ -131,9 +168,9 @@ class _ExpandedListState extends State<_ExpandedList> {
final apps = state.apps;
final expanded = state.expanded;
final selectedItems = state.selectedItems;
final isExpaned = expanded[index] == true;
final isExpanded = expanded[index] == true;
List<Widget> expandedChildren = [];
if (isExpaned) {
if (isExpanded) {
for (var i = 0; i < selectedItems[index].length; i++) {
final name = apps[index].belongings.items[i].name;
final checkbox = CheckboxListTile(
@@ -160,7 +197,7 @@ class _ExpandedListState extends State<_ExpandedList> {
child: ListTile(
title: FlowyText.medium(apps[index].name),
trailing: Icon(
isExpaned
isExpanded
? Icons.arrow_drop_down_rounded
: Icons.arrow_drop_up_rounded,
),
@@ -182,3 +219,54 @@ extension AppFlowy on dartz.Either {
return null;
}
}
class _AppFlowyFileExporter {
static Future<Tuple2<bool, List<String>>> exportToPath(
String path,
List<ViewPB> views,
) async {
final failedFileNames = <String>[];
final Map<String, int> names = {};
final documentService = DocumentService();
for (final view in views) {
String? content;
String? fileExtension;
switch (view.layout) {
case ViewLayoutTypePB.Document:
final document = await documentService.openDocument(view: view);
document.fold(
(l) => content = l.content,
(r) => Log.error(r),
);
fileExtension = 'afdoc';
break;
default:
final result = await exportDatabase(view.id);
result.fold(
(pb) => content = pb.data,
(r) => Log.error(r),
);
fileExtension = 'afdb';
break;
}
if (content != null) {
final count = names.putIfAbsent(view.name, () => 0);
final name = count == 0 ? view.name : '${view.name}($count)';
final file = File(p.join(path, '$name.$fileExtension'));
await file.writeAsString(content!);
names[view.name] = count + 1;
} else {
failedFileNames.add(view.name);
}
}
return Tuple2(failedFileNames.isEmpty, failedFileNames);
}
}
Future<dartz.Either<ExportCSVPB, FlowyError>> exportDatabase(
String viewId,
) async {
final payload = DatabaseViewIdPB.create()..value = viewId;
return DatabaseEventExportCSV(payload).send();
}
@@ -1,3 +1,4 @@
import 'package:appflowy/workspace/presentation/settings/widgets/settings_export_file_widget.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart';
import 'package:flutter/material.dart';
@@ -14,22 +15,20 @@ class SettingsFileSystemView extends StatefulWidget {
class _SettingsFileSystemViewState extends State<SettingsFileSystemView> {
final _locationCubit = SettingsLocationCubit()..fetchLocation();
late final _items = [
SettingsFileLocationCustomizer(
cubit: _locationCubit,
),
const SettingsExportFileWidget()
];
@override
Widget build(BuildContext context) {
return ListView.separated(
itemBuilder: (context, index) {
if (index == 0) {
return SettingsFileLocationCustomzier(
cubit: _locationCubit,
);
} else if (index == 1) {
// return _buildExportDatabaseButton();
}
return Container();
},
shrinkWrap: true,
itemBuilder: (context, index) => _items[index],
separatorBuilder: (context, index) => const Divider(),
itemCount: 2, // make the divider taking effect.
itemCount: _items.length,
);
}
}
@@ -588,6 +588,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -38,7 +38,7 @@ String languageFromLocale(Locale locale) {
case "hu":
return "Magyar";
case "id":
return "Bahasa";
return "Bahasa Indonesia";
case "it":
return "Italiano";
case "ja":
@@ -132,6 +132,21 @@ class FlowyHoverContainer extends StatelessWidget {
width: style.borderWidth,
);
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final iconTheme = theme.iconTheme;
// override text's theme with foregroundColorOnHover when it is hovered
final hoverTheme = theme.copyWith(
textTheme: textTheme.copyWith(
bodyMedium: textTheme.bodyMedium?.copyWith(
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
),
),
iconTheme: iconTheme.copyWith(
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
),
);
return Container(
margin: style.contentMargin,
decoration: BoxDecoration(
@@ -139,19 +154,8 @@ class FlowyHoverContainer extends StatelessWidget {
color: style.hoverColor ?? Theme.of(context).colorScheme.secondary,
borderRadius: style.borderRadius,
),
child:
//override text's theme with foregroundColorOnHover when it is hovered
Theme(
data: Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.copyWith(
bodyMedium: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: style.foregroundColorOnHover ??
Theme.of(context).colorScheme.onSurface),
),
iconTheme: Theme.of(context).iconTheme.copyWith(
color: style.foregroundColorOnHover ??
Theme.of(context).colorScheme.onSurface),
),
child: Theme(
data: hoverTheme,
child: child,
),
);
+2 -2
View File
@@ -45,10 +45,10 @@ packages:
dependency: "direct main"
description:
name: appflowy_editor
sha256: a1dbca3d7d33f4669f1d44bfa1e06b6646f46726c219921b8a59d9ee22bf252d
sha256: "6f7d2b0b54ca1049cb396229549d228b5bbd7ea6d09f1f7325a20db2d7586a5f"
url: "https://pub.dev"
source: hosted
version: "0.1.9"
version: "0.1.12"
appflowy_popover:
dependency: "direct main"
description:
+2 -2
View File
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.2
version: 0.1.6
environment:
sdk: ">=2.19.0 <3.0.0"
@@ -42,7 +42,7 @@ dependencies:
git:
url: https://github.com/AppFlowy-IO/appflowy-board.git
ref: a183c57
appflowy_editor: ^0.1.9
appflowy_editor: ^0.1.12
appflowy_popover:
path: packages/appflowy_popover
+26 -5
View File
@@ -749,6 +749,27 @@ dependencies = [
"syn",
]
[[package]]
name = "csv"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086"
dependencies = [
"csv-core",
"itoa 1.0.5",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
dependencies = [
"memchr",
]
[[package]]
name = "ctor"
version = "0.1.26"
@@ -1251,6 +1272,7 @@ dependencies = [
"bytes",
"chrono",
"crossbeam-utils",
"csv",
"dashmap",
"database-model",
"diesel",
@@ -2956,9 +2978,9 @@ dependencies = [
[[package]]
name = "openssl"
version = "0.10.45"
version = "0.10.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019"
dependencies = [
"bitflags",
"cfg-if",
@@ -2988,11 +3010,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.80"
version = "0.9.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
+1
View File
@@ -1141,6 +1141,7 @@ dependencies = [
"bytes",
"chrono",
"crossbeam-utils",
"csv",
"dashmap",
"database-model",
"diesel",
@@ -47,6 +47,7 @@ atomic_refcell = "0.1.9"
crossbeam-utils = "0.8.15"
async-stream = "0.3.4"
parking_lot = "0.12.1"
csv = "1.1.6"
[dev-dependencies]
flowy-test = { path = "../flowy-test" }
@@ -203,3 +203,9 @@ pub struct DatabaseLayoutIdPB {
#[pb(index = 2)]
pub layout: LayoutTypePB,
}
#[derive(Clone, ProtoBuf, Default, Debug)]
pub struct ExportCSVPB {
#[pb(index = 1)]
pub data: String,
}
@@ -1,6 +1,7 @@
use crate::entities::*;
use crate::manager::DatabaseManager;
use crate::services::cell::{FromCellString, ToCellChangesetString, TypeCellData};
use crate::services::export::CSVExport;
use crate::services::field::{
default_type_option_builder_from_type, select_type_option_from_field_rev,
type_option_builder_from_json_str, DateCellChangeset, DateChangesetPB, SelectOptionCellChangeset,
@@ -644,3 +645,16 @@ pub(crate) async fn get_calendar_event_handler(
Some(event) => data_result_ok(event),
}
}
#[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn export_csv_handler(
data: AFPluginData<DatabaseViewIdPB>,
manager: AFPluginState<Arc<DatabaseManager>>,
) -> DataResult<ExportCSVPB, FlowyError> {
let params = data.into_inner();
let database_editor = manager.get_database_editor(&params.value).await?;
let content = CSVExport
.export_database(&params.value, &database_editor)
.await?;
data_result_ok(ExportCSVPB { data: content })
}
@@ -55,7 +55,8 @@ pub fn init(database_manager: Arc<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::GetCalendarEvent, get_calendar_event_handler)
// Layout setting
.event(DatabaseEvent::SetLayoutSetting, set_layout_setting_handler)
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler);
.event(DatabaseEvent::GetLayoutSetting, get_layout_setting_handler)
.event(DatabaseEvent::ExportCSV, export_csv_handler);
plugin
}
@@ -256,4 +257,7 @@ pub enum DatabaseEvent {
#[event(input = "MoveCalendarEventPB")]
MoveCalendarEvent = 119,
#[event(input = "DatabaseViewIdPB", output = "ExportCSVPB")]
ExportCSV = 120,
}
@@ -0,0 +1,182 @@
use crate::entities::FieldType;
use crate::services::cell::TypeCellData;
use crate::services::database::DatabaseEditor;
use crate::services::field::{
CheckboxTypeOptionPB, ChecklistTypeOptionPB, DateCellData, DateTypeOptionPB,
MultiSelectTypeOptionPB, NumberTypeOptionPB, RichTextTypeOptionPB, SingleSelectTypeOptionPB,
URLCellData,
};
use database_model::{FieldRevision, TypeOptionDataDeserializer};
use flowy_error::{FlowyError, FlowyResult};
use indexmap::IndexMap;
use serde::Serialize;
use serde_json::{json, Map, Value};
use std::collections::HashMap;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize)]
pub struct ExportField {
pub id: String,
pub name: String,
pub field_type: i64,
pub visibility: bool,
pub width: i64,
pub type_options: HashMap<String, Value>,
pub is_primary: bool,
}
#[derive(Debug, Clone, Serialize)]
struct ExportCell {
data: String,
field_type: FieldType,
}
impl From<&Arc<FieldRevision>> for ExportField {
fn from(field_rev: &Arc<FieldRevision>) -> Self {
let field_type = FieldType::from(field_rev.ty);
let mut type_options: HashMap<String, Value> = HashMap::new();
field_rev
.type_options
.iter()
.filter(|(k, _)| k == &&field_rev.ty.to_string())
.for_each(|(k, s)| {
let value = match field_type {
FieldType::RichText => {
let pb = RichTextTypeOptionPB::from_json_str(s);
serde_json::to_value(pb).unwrap()
},
FieldType::Number => {
let pb = NumberTypeOptionPB::from_json_str(s);
let mut map = Map::new();
map.insert("format".to_string(), json!(pb.format as u8));
map.insert("scale".to_string(), json!(pb.scale));
map.insert("symbol".to_string(), json!(pb.symbol));
map.insert("name".to_string(), json!(pb.name));
Value::Object(map)
},
FieldType::DateTime => {
let pb = DateTypeOptionPB::from_json_str(s);
let mut map = Map::new();
map.insert("date_format".to_string(), json!(pb.date_format as u8));
map.insert("time_format".to_string(), json!(pb.time_format as u8));
map.insert("field_type".to_string(), json!(FieldType::DateTime as u8));
Value::Object(map)
},
FieldType::SingleSelect => {
let pb = SingleSelectTypeOptionPB::from_json_str(s);
let value = serde_json::to_string(&pb).unwrap();
let mut map = Map::new();
map.insert("content".to_string(), Value::String(value));
Value::Object(map)
},
FieldType::MultiSelect => {
let pb = MultiSelectTypeOptionPB::from_json_str(s);
let value = serde_json::to_string(&pb).unwrap();
let mut map = Map::new();
map.insert("content".to_string(), Value::String(value));
Value::Object(map)
},
FieldType::Checkbox => {
let pb = CheckboxTypeOptionPB::from_json_str(s);
serde_json::to_value(pb).unwrap()
},
FieldType::URL => {
let pb = RichTextTypeOptionPB::from_json_str(s);
serde_json::to_value(pb).unwrap()
},
FieldType::Checklist => {
let pb = ChecklistTypeOptionPB::from_json_str(s);
let value = serde_json::to_string(&pb).unwrap();
let mut map = Map::new();
map.insert("content".to_string(), Value::String(value));
Value::Object(map)
},
};
type_options.insert(k.clone(), value);
});
Self {
id: field_rev.id.clone(),
name: field_rev.name.clone(),
field_type: field_rev.ty as i64,
visibility: true,
width: 100,
type_options,
is_primary: field_rev.is_primary,
}
}
}
pub struct CSVExport;
impl CSVExport {
pub async fn export_database(
&self,
view_id: &str,
database_editor: &Arc<DatabaseEditor>,
) -> FlowyResult<String> {
let mut wtr = csv::Writer::from_writer(vec![]);
let row_revs = database_editor.get_all_row_revs(view_id).await?;
let field_revs = database_editor.get_field_revs(None).await?;
// Write fields
let field_records = field_revs
.iter()
.map(|field| ExportField::from(field))
.map(|field| serde_json::to_string(&field).unwrap())
.collect::<Vec<String>>();
wtr
.write_record(&field_records)
.map_err(|e| FlowyError::internal().context(e))?;
// Write rows
let mut field_by_field_id = IndexMap::new();
field_revs.into_iter().for_each(|field| {
field_by_field_id.insert(field.id.clone(), field);
});
for row_rev in row_revs {
let cells = field_by_field_id
.iter()
.map(|(field_id, field)| {
let field_type = FieldType::from(field.ty);
let data = row_rev
.cells
.get(field_id)
.map(|cell| TypeCellData::try_from(cell))
.map(|data| {
data
.map(|data| match field_type {
FieldType::DateTime => {
match serde_json::from_str::<DateCellData>(&data.cell_str) {
Ok(cell_data) => cell_data.timestamp.unwrap_or_default().to_string(),
Err(_) => "".to_string(),
}
},
FieldType::URL => match serde_json::from_str::<URLCellData>(&data.cell_str) {
Ok(cell_data) => cell_data.content,
Err(_) => "".to_string(),
},
_ => data.cell_str,
})
.unwrap_or_default()
})
.unwrap_or_else(|| "".to_string());
let cell = ExportCell { data, field_type };
serde_json::to_string(&cell).unwrap()
})
.collect::<Vec<_>>();
if let Err(e) = wtr.write_record(&cells) {
tracing::warn!("CSV failed to write record: {}", e);
}
}
let data = wtr
.into_inner()
.map_err(|e| FlowyError::internal().context(e))?;
let csv = String::from_utf8(data).map_err(|e| FlowyError::internal().context(e))?;
Ok(csv)
}
}
@@ -168,6 +168,7 @@ impl ToString for DateCellData {
}
#[derive(Clone, Debug, Copy, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
#[repr(u8)]
pub enum DateFormat {
Local = 0,
US = 1,
@@ -216,6 +217,7 @@ impl DateFormat {
#[derive(
Clone, Copy, PartialEq, Eq, EnumIter, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum,
)]
#[repr(u8)]
pub enum TimeFormat {
TwelveHour = 0,
TwentyFourHour = 1,
@@ -14,6 +14,7 @@ lazy_static! {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
#[repr(u8)]
pub enum NumberFormat {
Num = 0,
USD = 1,
@@ -216,7 +216,17 @@ impl TypeOptionCellDataCompare for NumberTypeOptionPB {
cell_data: &<Self as TypeOption>::CellData,
other_cell_data: &<Self as TypeOption>::CellData,
) -> Ordering {
cell_data.0.cmp(&other_cell_data.0)
let left = NumberCellData::from_format_str(&cell_data.0, self.sign_positive, &self.format);
let right =
NumberCellData::from_format_str(&other_cell_data.0, self.sign_positive, &self.format);
match (left, right) {
(Ok(left), Ok(right)) => {
return left.decimal().cmp(&right.decimal());
},
(Ok(_), Err(_)) => Ordering::Greater,
(Err(_), Ok(_)) => Ordering::Less,
(Err(_), Err(_)) => Ordering::Equal,
}
}
}
impl std::default::Default for NumberTypeOptionPB {
@@ -37,7 +37,7 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
pub struct RichTextTypeOptionPB {
#[pb(index = 1)]
#[serde(default)]
data: String,
pub data: String,
}
impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);
@@ -3,6 +3,7 @@ mod util;
pub mod cell;
pub mod database;
pub mod database_view;
pub mod export;
pub mod field;
pub mod filter;
pub mod group;
@@ -0,0 +1,23 @@
use crate::database::database_editor::DatabaseEditorTest;
use flowy_database::services::export::CSVExport;
#[tokio::test]
async fn export_test() {
let test = DatabaseEditorTest::new_grid().await;
let s = CSVExport
.export_database(&test.view_id, &test.editor)
.await
.unwrap();
let mut reader = csv::Reader::from_reader(s.as_bytes());
for header in reader.headers() {
println!("{:?}", header);
}
let export_csv_records = reader.records();
for record in export_csv_records {
let record = record.unwrap();
println!("{:?}", record);
}
}
@@ -161,7 +161,7 @@ pub fn make_test_grid() -> BuildDatabaseContext {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::Number => row_builder.insert_number_cell("14"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))
@@ -9,4 +9,5 @@ mod layout_test;
mod snapshot_test;
mod sort_test;
mod export_test;
mod mock_data;
@@ -212,7 +212,7 @@ async fn sort_number_by_descending_test() {
let scripts = vec![
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$1", "$2", "$3", "$4", "", "$5"],
orders: vec!["$1", "$2", "$3", "$14", "", "$5"],
},
InsertSort {
field_rev: number_field.clone(),
@@ -220,7 +220,7 @@ async fn sort_number_by_descending_test() {
},
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$5", "$4", "$3", "$2", "$1", ""],
orders: vec!["$14", "$5", "$3", "$2", "$1", ""],
},
];
test.run_scripts(scripts).await;
@@ -2,175 +2,78 @@
"document": {
"type": "editor",
"children": [
{ "type": "cover" },
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h1"
},
"attributes": { "subtype": "heading", "heading": "h1" },
"delta": [{ "insert": "Welcome to AppFlowy!" }]
},
{
"type": "text",
"attributes": { "subtype": "heading", "heading": "h2" },
"delta": [{ "insert": "Here are the basics" }]
},
{
"type": "text",
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [{ "insert": "Click anywhere and just start typing." }]
},
{
"type": "text",
"attributes": { "subtype": "checkbox", "checkbox": false },
"delta": [
{
"insert": "🌟 Welcome to AppFlowy!"
}
"insert": "Highlight ",
"attributes": { "backgroundColor": "0x4dffeb3b" }
},
{ "insert": "any text, and use the editing menu to " },
{ "insert": "style", "attributes": { "italic": true } },
{ "insert": " " },
{ "insert": "your", "attributes": { "bold": true } },
{ "insert": " " },
{ "insert": "writing", "attributes": { "underline": true } },
{ "insert": " " },
{ "insert": "however", "attributes": { "code": true } },
{ "insert": " you " },
{ "insert": "like.", "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h2"
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Here are the basics"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "Click anywhere and just start typing."
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "Highlight",
"attributes": {
"backgroundColor": "0x6000BCF0"
}
},
{
"insert": " any text, and use the editing menu to "
},
{
"insert": "style",
"attributes": {
"italic": true
}
},
{
"insert": " "
},
{
"insert": "your",
"attributes": {
"bold": true
}
},
{
"insert": " "
},
{
"insert": "writing",
"attributes": {
"underline": true
}
},
{
"insert": " "
},
{
"insert": "however",
"attributes": {
"code": true
}
},
{
"insert": " you "
},
{
"insert": "like.",
"attributes": {
"strikethrough": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "As soon as you type "
},
{ "insert": "As soon as you type " },
{
"insert": "/",
"attributes": {
"code": true
}
"attributes": { "code": true, "color": "0xff00b5ff" }
},
{ "insert": " a menu will pop up. Select " },
{
"insert": " a menu will pop up. Select different types of content blocks you can add."
}
"insert": "different types",
"attributes": { "backgroundColor": "0x4d9c27b0" }
},
{ "insert": " of content blocks you can add." }
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Type "
},
{
"insert": "/",
"attributes": {
"code": true
}
},
{
"insert": " followed by "
},
{
"insert": "/bullet",
"attributes": {
"code": true
}
},
{
"insert": " or "
},
{
"insert": "/c.",
"attributes": {
"code": true
}
}
{ "insert": "Type " },
{ "insert": "/", "attributes": { "code": true } },
{ "insert": " followed by " },
{ "insert": "/bullet", "attributes": { "code": true } },
{ "insert": " or " },
{ "insert": "/num", "attributes": { "code": true } },
{ "insert": " to create a list.", "attributes": { "code": false } }
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": true
},
"attributes": { "subtype": "checkbox", "checkbox": true },
"delta": [
{
"insert": "Click "
},
{
"insert": "+ New Page ",
"attributes": {
"code": true
}
},
{ "insert": "Click " },
{ "insert": "+ New Page ", "attributes": { "code": true } },
{
"insert": "button at the bottom of your sidebar to add a new page."
}
@@ -178,32 +81,24 @@
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Click "
},
{
"insert": "+",
"attributes": {
"code": true
}
},
{
"insert": " next to any page title in the sidebar to quickly add a new subpage."
}
{ "insert": "Click " },
{ "insert": "+", "attributes": { "code": true } },
{ "insert": " next to any page title in the sidebar to " },
{ "insert": "quickly", "attributes": { "color": "0xff8427e0" } },
{ "insert": " add a new subpage, " },
{ "insert": "Document", "attributes": { "code": true } },
{ "insert": ", ", "attributes": { "code": false } },
{ "insert": "Grid", "attributes": { "code": true } },
{ "insert": ", or ", "attributes": { "code": false } },
{ "insert": "Kanban Board", "attributes": { "code": true } },
{ "insert": ".", "attributes": { "code": false } }
]
},
{
"type": "text",
"attributes": {
"checkbox": null
},
"delta": []
},
{ "type": "text", "delta": [] },
{ "type": "divider" },
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
{
"type": "text",
"attributes": {
@@ -211,11 +106,7 @@
"checkbox": null,
"heading": "h2"
},
"delta": [
{
"insert": "Markdown"
}
]
"delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }]
},
{
"type": "text",
@@ -225,98 +116,64 @@
"heading": null
},
"delta": [
{ "insert": "Keyboard shortcuts " },
{
"insert": "Heading "
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 2
},
"delta": [
{
"insert": "bold text",
"insert": "guide",
"attributes": {
"bold": true,
"defaultFormatting": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 3
},
"delta": [
{
"insert": "italicized text",
"attributes": {
"italic": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 4,
"number-list": null
},
"delta": [
{
"insert": "Ordered List"
}
]
},
{
"type": "text",
"attributes": {
"number": 5,
"subtype": "number-list"
},
"delta": [
{
"insert": "code",
"attributes": {
"code": true
}
}
]
},
{
"type": "text",
"attributes": {
"number": 6,
"subtype": "number-list"
},
"delta": [
{
"insert": "Strikethrough",
"attributes": {
"strikethrough": true
"href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
}
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 2,
"heading": null
},
"delta": [
{ "insert": "Markdown " },
{
"retain": 1,
"insert": "reference",
"attributes": {
"strikethrough": true
"href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
}
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": { "number": 3, "subtype": "number-list" },
"delta": [
{ "insert": "Type " },
{ "insert": "/code", "attributes": { "code": true } },
{
"insert": " to insert a code block",
"attributes": { "code": false }
}
]
},
{
"type": "text",
"attributes": {
"checkbox": null
"subtype": "code_block",
"number": 3,
"heading": null,
"number-list": null,
"theme": "vs",
"language": "rust"
},
"delta": []
"delta": [
{
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
{
"type": "text",
"attributes": {
@@ -324,94 +181,72 @@
"checkbox": null,
"heading": "h2"
},
"delta": [
{
"insert": "Have a question?"
}
]
"delta": [{ "insert": "Have a question❓" }]
},
{
"type": "text",
"attributes": {
"subtype": "quote"
},
"attributes": { "subtype": "quote" },
"delta": [
{
"insert": "Click "
},
{
"insert": "?",
"attributes": {
"code": true
}
},
{
"insert": " at the bottom right for help and support."
}
{ "insert": "Click " },
{ "insert": "?", "attributes": { "code": true } },
{ "insert": " at the bottom right for help and support." }
]
},
{ "type": "text", "delta": [] },
{
"type": "callout",
"children": [
{ "type": "text", "delta": [] },
{
"type": "text",
"attributes": { "subtype": "heading", "heading": "h2" },
"delta": [{ "insert": "Like AppFlowy? Follow us:" }]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "GitHub",
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
}
}
]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "Twitter",
"attributes": { "href": "https://twitter.com/appflowy" }
},
{ "insert": ": @appflowy" }
]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "Newsletter",
"attributes": { "href": "https://blog-appflowy.ghost.io/" }
}
]
}
],
"attributes": { "emoji": "🥰" }
},
{ "type": "text", "delta": [] },
{
"type": "text",
"attributes": { "subtype": null, "heading": null },
"delta": []
},
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h2"
},
"delta": [
{
"insert": "Like AppFlowy? Follow us:"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list",
"quote": null
},
"delta": [
{
"insert": "GitHub",
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list"
},
"delta": [
{
"insert": "Twitter: @appflowy"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list"
},
"delta": [
{
"insert": "Newsletter",
"attributes": {
"href": "https://blog-appflowy.ghost.io/"
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": null,
"heading": null
},
"attributes": { "subtype": null, "heading": null },
"delta": []
}
]
@@ -23,6 +23,11 @@ args[1]: The appflowy version to be built (github ref_name).
assert(await repositoryRoot.exists(),
'$repositoryRoot is an invalid directory. Please try again with a valid directory.\n\n$help');
final appVersion = args[1];
await _BuildTool(repositoryRoot: repositoryRoot.path, appVersion: appVersion)
.run();
String? arch;
if (args.length > 2) arch = args[2];
await _BuildTool(
repositoryRoot: repositoryRoot.path,
appVersion: appVersion,
arch: arch,
).run();
}
@@ -14,10 +14,12 @@ class _BuildTool {
const _BuildTool({
required this.repositoryRoot,
required this.appVersion,
this.arch,
});
final String repositoryRoot;
final String appVersion;
final String? arch;
String get projectRoot =>
[repositoryRoot, 'appflowy_flutter'].join(Platform.pathSeparator);
@@ -30,8 +32,9 @@ class _BuildTool {
Future<String> get _commandForOS async {
// Check the operating system and CPU architecture
var os = Platform.operatingSystem;
var arch = Platform.isMacOS ? await _architecture : Platform.localHostname;
final os = Platform.operatingSystem;
final arch = this.arch ??
(Platform.isMacOS ? await _architecture : Platform.localHostname);
// Determine the appropriate command based on the OS and architecture
if (os == 'windows') {
@@ -1,6 +1,7 @@
[Setup]
AppName=AppFlowy
AppVersion={#AppVersion}
AppPublisher=AppFlowy-IO
WizardStyle=modern
Compression=lzma2
SolidCompression=yes
@@ -9,8 +10,8 @@ DefaultGroupName=AppFlowy
SetupIconFile=flowy_logo.ico
UninstallDisplayIcon={app}\AppFlowy.exe
UninstallDisplayName=AppFlowy
AppPublisher=AppFlowy-IO
VersionInfoVersion={#AppVersion}
UsePreviousAppDir=no
[Files]
Source: "AppFlowy\AppFlowy.exe";DestDir: "{app}";DestName: "AppFlowy.exe"
@@ -18,4 +19,5 @@ Source: "AppFlowy\*";DestDir: "{app}"
Source: "AppFlowy\data\*";DestDir: "{app}\data\"; Flags: recursesubdirs
[Icons]
Name: "{group}\AppFlowy";Filename: "{app}\AppFlowy.exe"
Name: "{userdesktop}\AppFlowy"; Filename: "{app}\AppFlowy.exe"
Name: "{group}\AppFlowy"; Filename: "{app}\AppFlowy.exe"
+14 -26
View File
@@ -1,29 +1,17 @@
// @ts-check
/**
* @type { import("@inlang/core/config").DefineConfig }
*/
export async function defineConfig(env) {
const plugin = await env.$import(
"https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@1.1.1/dist/index.js"
);
const { default: pluginJson } = await env.$import(
'https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@2/dist/index.js'
);
const { default: standardLintRules } = await env.$import(
"https://cdn.jsdelivr.net/gh/inlang/standard-lint-rules@1.1.1/dist/index.js"
);
const { default: standardLintRules } = await env.$import(
'https://cdn.jsdelivr.net/gh/inlang/standard-lint-rules@2/dist/index.js'
);
const pluginConfig = {
pathPattern: "./frontend/appflowy_flutter/assets/translations/{language}.json",
};
return {
referenceLanguage: "en",
languages: await plugin.getLanguages({ ...env, pluginConfig }),
readResources: (args) => plugin.readResources({ ...args, ...env, pluginConfig }),
writeResources: (args) => plugin.writeResources({ ...args, ...env, pluginConfig }),
lint: {
rules: [standardLintRules()],
},
};
}
return {
referenceLanguage: 'en',
plugins: [pluginJson({
pathPattern: './frontend/appflowy_flutter/assets/translations/{language}.json',
variableReferencePattern: ["@:"]
}), standardLintRules()]
};
}