diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a4582ffa74..918a2018f7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -338,7 +338,7 @@ jobs:
- {
arch: x86_64,
target: x86_64-unknown-linux-gnu,
- os: ubuntu-22.04,
+ os: ubuntu-20.04,
extra-build-args: "",
flutter_profile: production-linux-x86_64,
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5e7e268a5..be3a1e8b8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,49 +1,4 @@
# Release Notes
-## Version 0.8.9 - 16/04/2025
-### Desktop
-#### New Features
-- Supported pasting a link as a mention, providing a more condensed visualization of linked content
-- Supported converting between link formats (e.g. transforming a mention into a bookmark)
-- Improved the link editing experience with enhanced UX
-- Added OTP (One-Time Password) support for sign-in authentication
-- Added latest AI models: GPT-4.1, GPT-4.1-mini, and Claude 3.7 Sonnet
-#### Bug Fixes
-- Fixed an issue where properties were not displaying in the row detail page
-- Fixed a bug where Undo didn't work in the row detail page
-- Fixed an issue where blocks didn't grow when the grid got bigger
-- Fixed several bugs related to AI writers
-### Mobile
-#### New Features
-- Added sign-in with OTP (One-Time Password)
-#### Bug Fixes
-- Fixed an issue where the slash menu sometimes failed to display
-- Updated the mention page block to handle page selection with more context.
-
-## Version 0.8.8 - 01/04/2025
-### New Features
-- Added support for selecting AI models in AI writer
-- Revamped link menu in toolbar
-- Added support for using ":" to add emojis in documents
-- Passed the history of past AI prompts and responses to AI writer
-### Bug Fixes
-- Improved AI writer scrolling user experience
-- Fixed issue where checklist items would disappear during reordering
-- Fixed numbered lists generated by AI to maintain the same index as the input
-
-## Version 0.8.7 - 18/03/2025
-### New Features
-- Made local AI free and integrated with Ollama
-- Supported nested lists within callout and quote blocks
-- Revamped the document's floating toolbar and added Turn Into
-- Enabled custom icons in callout blocks
-### Bug Fixes
-- Fixed occasional incorrect positioning of the slash menu
-- Improved AI Chat and AI Writers with various bug fixes
-- Adjusted the columns block to match the width of the editor
-- Fixed a potential segfault caused by infinite recursion in the trash view
-- Resolved an issue where the first added cover might be invisible
-- Fixed adding cover images via Unsplash
-
## Version 0.8.6 - 06/03/2025
### Bug Fixes
- Fix the incorrect title positioning when adjusting the document width setting
diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml
index 41fdffb1af..89ee36ad6a 100644
--- a/frontend/Makefile.toml
+++ b/frontend/Makefile.toml
@@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
-APPFLOWY_VERSION = "0.8.9"
+APPFLOWY_VERSION = "0.8.6"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"
diff --git a/frontend/appflowy_flutter/analysis_options.yaml b/frontend/appflowy_flutter/analysis_options.yaml
index 4579b2d8c5..8da401ef26 100644
--- a/frontend/appflowy_flutter/analysis_options.yaml
+++ b/frontend/appflowy_flutter/analysis_options.yaml
@@ -4,7 +4,6 @@ analyzer:
exclude:
- "**/*.g.dart"
- "**/*.freezed.dart"
- - "packages/**/*.dart"
linter:
rules:
diff --git a/frontend/appflowy_flutter/assets/fonts/.gitkeep b/frontend/appflowy_flutter/assets/fonts/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf b/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf
new file mode 100644
index 0000000000..8f03a5c8f9
Binary files /dev/null and b/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf differ
diff --git a/frontend/appflowy_flutter/assets/translations/mr-IN.json b/frontend/appflowy_flutter/assets/translations/mr-IN.json
deleted file mode 100644
index f86a1e0081..0000000000
--- a/frontend/appflowy_flutter/assets/translations/mr-IN.json
+++ /dev/null
@@ -1,3210 +0,0 @@
-{
- "appName": "AppFlowy",
- "defaultUsername": "मी",
- "welcomeText": "@:appName मध्ये आ पले स्वागत आहे.",
- "welcomeTo": "मध्ये आ पले स्वागत आ हे",
- "githubStarText": "GitHub वर स्टार करा",
- "subscribeNewsletterText": "वृत्तपत्राची सदस्यता घ्या",
- "letsGoButtonText": "क्विक स्टार्ट",
- "title": "Title",
- "youCanAlso": "तुम्ही देखील",
- "and": "आ णि",
- "failedToOpenUrl": "URL उघडण्यात अयशस्वी: {}",
- "blockActions": {
- "addBelowTooltip": "खाली जोडण्यासाठी क्लिक करा",
- "addAboveCmd": "Alt+click",
- "addAboveMacCmd": "Option+click",
- "addAboveTooltip": "वर जोडण्यासाठी",
- "dragTooltip": "Drag to move",
- "openMenuTooltip": "मेनू उघडण्यासाठी क्लिक करा"
- },
- "signUp": {
- "buttonText": "साइन अप",
- "title": "साइन अप to @:appName",
- "getStartedText": "सुरुवात करा",
- "emptyPasswordError": "पासवर्ड रिकामा असू शकत नाही",
- "repeatPasswordEmptyError": "Repeat पासवर्ड रिकामा असू शकत नाही",
- "unmatchedPasswordError": "पुन्हा लिहिलेला पासवर्ड मूळ पासवर्डशी जुळत नाही",
- "alreadyHaveAnAccount": "आधीच खाते आहे?",
- "emailHint": "Email",
- "passwordHint": "Password",
- "repeatPasswordHint": "पासवर्ड पुन्हा लिहा",
- "signUpWith": "यामध्ये साइन अप करा:"
- },
- "signIn": {
- "loginTitle": "@:appName मध्ये लॉगिन करा",
- "loginButtonText": "लॉगिन",
- "loginStartWithAnonymous": "अनामिक सत्रासह पुढे जा",
- "continueAnonymousUser": "अनामिक सत्रासह पुढे जा",
- "anonymous": "अनामिक",
- "buttonText": "साइन इन",
- "signingInText": "साइन इन होत आहे...",
- "forgotPassword": "पासवर्ड विसरलात?",
- "emailHint": "ईमेल",
- "passwordHint": "पासवर्ड",
- "dontHaveAnAccount": "तुमचं खाते नाही?",
- "createAccount": "खाते तयार करा",
- "repeatPasswordEmptyError": "पुन्हा पासवर्ड रिकामा असू शकत नाही",
- "unmatchedPasswordError": "पुन्हा लिहिलेला पासवर्ड मूळ पासवर्डशी जुळत नाही",
- "syncPromptMessage": "डेटा सिंक होण्यास थोडा वेळ लागू शकतो. कृपया हे पृष्ठ बंद करू नका",
- "or": "किंवा",
- "signInWithGoogle": "Google सह पुढे जा",
- "signInWithGithub": "GitHub सह पुढे जा",
- "signInWithDiscord": "Discord सह पुढे जा",
- "signInWithApple": "Apple सह पुढे जा",
- "continueAnotherWay": "इतर पर्यायांनी पुढे जा",
- "signUpWithGoogle": "Google सह साइन अप करा",
- "signUpWithGithub": "GitHub सह साइन अप करा",
- "signUpWithDiscord": "Discord सह साइन अप करा",
- "signInWith": "यासह पुढे जा:",
- "signInWithEmail": "ईमेलसह पुढे जा",
- "signInWithMagicLink": "पुढे जा",
- "signUpWithMagicLink": "Magic Link सह साइन अप करा",
- "pleaseInputYourEmail": "कृपया तुमचा ईमेल पत्ता टाका",
- "settings": "सेटिंग्ज",
- "magicLinkSent": "Magic Link पाठवण्यात आली आहे!",
- "invalidEmail": "कृपया वैध ईमेल पत्ता टाका",
- "alreadyHaveAnAccount": "आधीच खाते आहे?",
- "logIn": "लॉगिन",
- "generalError": "काहीतरी चुकलं. कृपया नंतर प्रयत्न करा",
- "limitRateError": "सुरक्षेच्या कारणास्तव, तुम्ही दर ६० सेकंदांतून एकदाच Magic Link मागवू शकता",
- "magicLinkSentDescription": "तुमच्या ईमेलवर Magic Link पाठवण्यात आली आहे. लॉगिन पूर्ण करण्यासाठी लिंकवर क्लिक करा. ही लिंक ५ मिनिटांत कालबाह्य होईल."
- },
- "workspace": {
- "chooseWorkspace": "तुमचे workspace निवडा",
- "defaultName": "माझे Workspace",
- "create": "नवीन workspace तयार करा",
- "new": "नवीन workspace",
- "importFromNotion": "Notion मधून आयात करा",
- "learnMore": "अधिक जाणून घ्या",
- "reset": "workspace रीसेट करा",
- "renameWorkspace": "workspace चे नाव बदला",
- "workspaceNameCannotBeEmpty": "workspace चे नाव रिकामे असू शकत नाही",
- "resetWorkspacePrompt": "workspace रीसेट केल्यास त्यातील सर्व पृष्ठे आणि डेटा हटवले जातील. तुम्हाला workspace रीसेट करायचे आहे का? पर्यायी म्हणून तुम्ही सपोर्ट टीमशी संपर्क करू शकता.",
- "hint": "workspace",
- "notFoundError": "workspace सापडले नाही",
- "failedToLoad": "काहीतरी चूक झाली! workspace लोड होण्यात अयशस्वी. कृपया @:appName चे कोणतेही उघडे instance बंद करा आणि पुन्हा प्रयत्न करा.",
- "errorActions": {
- "reportIssue": "समस्या नोंदवा",
- "reportIssueOnGithub": "Github वर समस्या नोंदवा",
- "exportLogFiles": "लॉग फाइल्स निर्यात करा",
- "reachOut": "Discord वर संपर्क करा"
- },
- "menuTitle": "कार्यक्षेत्रे",
- "deleteWorkspaceHintText": "तुम्हाला हे कार्यक्षेत्र हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही आणि तुम्ही प्रकाशित केलेली कोणतीही पृष्ठे अप्रकाशित होतील.",
- "createSuccess": "कार्यक्षेत्र यशस्वीरित्या तयार झाले",
- "createFailed": "कार्यक्षेत्र तयार करण्यात अयशस्वी",
- "createLimitExceeded": "तुम्ही तुमच्या खात्यासाठी परवानगी दिलेल्या कार्यक्षेत्र मर्यादेपर्यंत पोहोचलात. अधिक कार्यक्षेत्रासाठी GitHub वर विनंती करा.",
- "deleteSuccess": "कार्यक्षेत्र यशस्वीरित्या हटवले गेले",
- "deleteFailed": "कार्यक्षेत्र हटवण्यात अयशस्वी",
- "openSuccess": "कार्यक्षेत्र यशस्वीरित्या उघडले",
- "openFailed": "कार्यक्षेत्र उघडण्यात अयशस्वी",
- "renameSuccess": "कार्यक्षेत्राचे नाव यशस्वीरित्या बदलले",
- "renameFailed": "कार्यक्षेत्राचे नाव बदलण्यात अयशस्वी",
- "updateIconSuccess": "कार्यक्षेत्राचे चिन्ह यशस्वीरित्या अद्यतनित केले",
- "updateIconFailed": "कार्यक्षेत्राचे चिन्ह अद्यतनित करण्यात अयशस्वी",
- "cannotDeleteTheOnlyWorkspace": "फक्त एकच कार्यक्षेत्र असल्यास ते हटवता येत नाही",
- "fetchWorkspacesFailed": "कार्यक्षेत्रे मिळवण्यात अयशस्वी",
- "leaveCurrentWorkspace": "कार्यक्षेत्र सोडा",
- "leaveCurrentWorkspacePrompt": "तुम्हाला हे कार्यक्षेत्र सोडायचे आहे का?"
- },
- "shareAction": {
- "buttonText": "शेअर करा",
- "workInProgress": "लवकरच येत आहे",
- "markdown": "Markdown",
- "html": "HTML",
- "clipboard": "क्लिपबोर्डवर कॉपी करा",
- "csv": "CSV",
- "copyLink": "लिंक कॉपी करा",
- "publishToTheWeb": "वेबवर प्रकाशित करा",
- "publishToTheWebHint": "AppFlowy सह वेबसाइट तयार करा",
- "publish": "प्रकाशित करा",
- "unPublish": "अप्रकाशित करा",
- "visitSite": "साइटला भेट द्या",
- "exportAsTab": "या स्वरूपात निर्यात करा",
- "publishTab": "प्रकाशित करा",
- "shareTab": "शेअर करा",
- "publishOnAppFlowy": "AppFlowy वर प्रकाशित करा",
- "shareTabTitle": "सहकार्य करण्यासाठी आमंत्रित करा",
- "shareTabDescription": "कोणासही सहज सहकार्य करण्यासाठी",
- "copyLinkSuccess": "लिंक क्लिपबोर्डवर कॉपी केली",
- "copyShareLink": "शेअर लिंक कॉपी करा",
- "copyLinkFailed": "लिंक क्लिपबोर्डवर कॉपी करण्यात अयशस्वी",
- "copyLinkToBlockSuccess": "ब्लॉकची लिंक क्लिपबोर्डवर कॉपी केली",
- "copyLinkToBlockFailed": "ब्लॉकची लिंक क्लिपबोर्डवर कॉपी करण्यात अयशस्वी",
- "manageAllSites": "सर्व साइट्स व्यवस्थापित करा",
- "updatePathName": "पथाचे नाव अपडेट करा"
- },
- "moreAction": {
- "small": "लहान",
- "medium": "मध्यम",
- "large": "मोठा",
- "fontSize": "फॉन्ट आकार",
- "import": "Import",
- "moreOptions": "अधिक पर्याय",
- "wordCount": "शब्द संख्या: {}",
- "charCount": "अक्षर संख्या: {}",
- "createdAt": "निर्मिती: {}",
- "deleteView": "हटवा",
- "duplicateView": "प्रत बनवा",
- "wordCountLabel": "शब्द संख्या: ",
- "charCountLabel": "अक्षर संख्या: ",
- "createdAtLabel": "निर्मिती: ",
- "syncedAtLabel": "सिंक केले: ",
- "saveAsNewPage": "संदेश पृष्ठात जोडा",
- "saveAsNewPageDisabled": "उपलब्ध संदेश नाहीत"
- },
- "importPanel": {
- "textAndMarkdown": "मजकूर आणि Markdown",
- "documentFromV010": "v0.1.0 पासून दस्तऐवज",
- "databaseFromV010": "v0.1.0 पासून डेटाबेस",
- "notionZip": "Notion निर्यात केलेली Zip फाईल",
- "csv": "CSV",
- "database": "डेटाबेस"
- },
- "emojiIconPicker": {
- "iconUploader": {
- "placeholderLeft": "फाईल ड्रॅग आणि ड्रॉप करा, क्लिक करा ",
- "placeholderUpload": "अपलोड",
- "placeholderRight": ", किंवा इमेज लिंक पेस्ट करा.",
- "dropToUpload": "अपलोडसाठी फाईल ड्रॉप करा",
- "change": "बदला"
- }
- },
- "disclosureAction": {
- "rename": "नाव बदला",
- "delete": "हटवा",
- "duplicate": "प्रत बनवा",
- "unfavorite": "आवडतीतून काढा",
- "favorite": "आवडतीत जोडा",
- "openNewTab": "नवीन टॅबमध्ये उघडा",
- "moveTo": "या ठिकाणी हलवा",
- "addToFavorites": "आवडतीत जोडा",
- "copyLink": "लिंक कॉपी करा",
- "changeIcon": "आयकॉन बदला",
- "collapseAllPages": "सर्व उपपृष्ठे संकुचित करा",
- "movePageTo": "पृष्ठ हलवा",
- "move": "हलवा",
- "lockPage": "पृष्ठ लॉक करा"
- },
- "blankPageTitle": "रिक्त पृष्ठ",
- "newPageText": "नवीन पृष्ठ",
- "newDocumentText": "नवीन दस्तऐवज",
- "newGridText": "नवीन ग्रिड",
- "newCalendarText": "नवीन कॅलेंडर",
- "newBoardText": "नवीन बोर्ड",
- "chat": {
- "newChat": "AI गप्पा",
- "inputMessageHint": "@:appName AI ला विचार करा",
- "inputLocalAIMessageHint": "@:appName लोकल AI ला विचार करा",
- "unsupportedCloudPrompt": "ही सुविधा फक्त @:appName Cloud वापरताना उपलब्ध आहे",
- "relatedQuestion": "सूचवलेले",
- "serverUnavailable": "कनेक्शन गमावले. कृपया तुमचे इंटरनेट तपासा आणि पुन्हा प्रयत्न करा",
- "aiServerUnavailable": "AI सेवा सध्या अनुपलब्ध आहे. कृपया नंतर पुन्हा प्रयत्न करा.",
- "retry": "पुन्हा प्रयत्न करा",
- "clickToRetry": "पुन्हा प्रयत्न करण्यासाठी क्लिक करा",
- "regenerateAnswer": "उत्तर पुन्हा तयार करा",
- "question1": "Kanban वापरून कामे कशी व्यवस्थापित करायची",
- "question2": "GTD पद्धत समजावून सांगा",
- "question3": "Rust का वापरावा",
- "question4": "माझ्या स्वयंपाकघरात असलेल्या वस्तूंनी रेसिपी",
- "question5": "माझ्या पृष्ठासाठी एक चित्र तयार करा",
- "question6": "या आठवड्याची माझी कामांची यादी तयार करा",
- "aiMistakePrompt": "AI चुकू शकतो. महत्त्वाची माहिती तपासा.",
- "chatWithFilePrompt": "तुम्हाला फाइलसोबत गप्पा मारायच्या आहेत का?",
- "indexFileSuccess": "फाईल यशस्वीरित्या अनुक्रमित केली गेली",
- "inputActionNoPages": "काहीही पृष्ठे सापडली नाहीत",
- "referenceSource": {
- "zero": "0 स्रोत सापडले",
- "one": "{count} स्रोत सापडला",
- "other": "{count} स्रोत सापडले"
- }
- },
- "clickToMention": "पृष्ठाचा उल्लेख करा",
- "uploadFile": "PDFs, मजकूर किंवा markdown फाइल्स जोडा",
- "questionDetail": "नमस्कार {}! मी तुम्हाला कशी मदत करू शकतो?",
- "indexingFile": "{} अनुक्रमित करत आहे",
- "generatingResponse": "उत्तर तयार होत आहे",
- "selectSources": "स्रोत निवडा",
- "currentPage": "सध्याचे पृष्ठ",
- "sourcesLimitReached": "तुम्ही फक्त ३ मुख्य दस्तऐवज आणि त्यांची उपदस्तऐवज निवडू शकता",
- "sourceUnsupported": "सध्या डेटाबेससह चॅटिंगसाठी आम्ही समर्थन करत नाही",
- "regenerate": "पुन्हा प्रयत्न करा",
- "addToPageButton": "संदेश पृष्ठावर जोडा",
- "addToPageTitle": "या पृष्ठात संदेश जोडा...",
- "addToNewPage": "नवीन पृष्ठ तयार करा",
- "addToNewPageName": "\"{}\" मधून काढलेले संदेश",
- "addToNewPageSuccessToast": "संदेश जोडण्यात आला",
- "openPagePreviewFailedToast": "पृष्ठ उघडण्यात अयशस्वी",
- "changeFormat": {
- "actionButton": "फॉरमॅट बदला",
- "confirmButton": "या फॉरमॅटसह पुन्हा तयार करा",
- "textOnly": "मजकूर",
- "imageOnly": "फक्त प्रतिमा",
- "textAndImage": "मजकूर आणि प्रतिमा",
- "text": "परिच्छेद",
- "bullet": "बुलेट यादी",
- "number": "क्रमांकित यादी",
- "table": "सारणी",
- "blankDescription": "उत्तराचे फॉरमॅट ठरवा",
- "defaultDescription": "स्वयंचलित उत्तर फॉरमॅट",
- "textWithImageDescription": "@:chat.changeFormat.text प्रतिमेसह",
- "numberWithImageDescription": "@:chat.changeFormat.number प्रतिमेसह",
- "bulletWithImageDescription": "@:chat.changeFormat.bullet प्रतिमेसह",
- " tableWithImageDescription": "@:chat.changeFormat.table प्रतिमेसह"
- },
- "switchModel": {
- "label": "मॉडेल बदला",
- "localModel": "स्थानिक मॉडेल",
- "cloudModel": "क्लाऊड मॉडेल",
- "autoModel": "स्वयंचलित"
- },
- "selectBanner": {
- "saveButton": "… मध्ये जोडा",
- "selectMessages": "संदेश निवडा",
- "nSelected": "{} निवडले गेले",
- "allSelected": "सर्व निवडले गेले"
- },
- "stopTooltip": "उत्पन्न करणे थांबवा",
- "trash": {
- "text": "कचरा",
- "restoreAll": "सर्व पुनर्संचयित करा",
- "restore": "पुनर्संचयित करा",
- "deleteAll": "सर्व हटवा",
- "pageHeader": {
- "fileName": "फाईलचे नाव",
- "lastModified": "शेवटचा बदल",
- "created": "निर्मिती"
- }
- },
- "confirmDeleteAll": {
- "title": "कचरापेटीतील सर्व पृष्ठे",
- "caption": "तुम्हाला कचरापेटीतील सर्व काही हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही."
- },
- "confirmRestoreAll": {
- "title": "कचरापेटीतील सर्व पृष्ठे पुनर्संचयित करा",
- "caption": "ही कृती पूर्ववत केली जाऊ शकत नाही."
- },
- "restorePage": {
- "title": "पुनर्संचयित करा: {}",
- "caption": "तुम्हाला हे पृष्ठ पुनर्संचयित करायचे आहे का?"
- },
- "mobile": {
- "actions": "कचरा क्रिया",
- "empty": "कचरापेटीत कोणतीही पृष्ठे किंवा जागा नाहीत",
- "emptyDescription": "जे आवश्यक नाही ते कचरापेटीत हलवा.",
- "isDeleted": "हटवले गेले आहे",
- "isRestored": "पुनर्संचयित केले गेले आहे"
- },
- "confirmDeleteTitle": "तुम्हाला हे पृष्ठ कायमचे हटवायचे आहे का?",
- "deletePagePrompt": {
- "text": "हे पृष्ठ कचरापेटीत आहे",
- "restore": "पृष्ठ पुनर्संचयित करा",
- "deletePermanent": "कायमचे हटवा",
- "deletePermanentDescription": "तुम्हाला हे पृष्ठ कायमचे हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही."
- },
- "dialogCreatePageNameHint": "पृष्ठाचे नाव",
- "questionBubble": {
- "shortcuts": "शॉर्टकट्स",
- "whatsNew": "नवीन काय आहे?",
- "help": "मदत आणि समर्थन",
- "markdown": "Markdown",
- "debug": {
- "name": "डीबग माहिती",
- "success": "डीबग माहिती क्लिपबोर्डवर कॉपी केली!",
- "fail": "डीबग माहिती कॉपी करता आली नाही"
- },
- "feedback": "अभिप्राय"
- },
- "menuAppHeader": {
- "moreButtonToolTip": "हटवा, नाव बदला आणि अधिक...",
- "addPageTooltip": "तत्काळ एक पृष्ठ जोडा",
- "defaultNewPageName": "शीर्षक नसलेले",
- "renameDialog": "नाव बदला",
- "pageNameSuffix": "प्रत"
- },
- "noPagesInside": "अंदर कोणतीही पृष्ठे नाहीत",
- "toolbar": {
- "undo": "पूर्ववत करा",
- "redo": "पुन्हा करा",
- "bold": "ठळक",
- "italic": "तिरकस",
- "underline": "अधोरेखित",
- "strike": "मागे ओढलेले",
- "numList": "क्रमांकित यादी",
- "bulletList": "बुलेट यादी",
- "checkList": "चेक यादी",
- "inlineCode": "इनलाइन कोड",
- "quote": "उद्धरण ब्लॉक",
- "header": "शीर्षक",
- "highlight": "हायलाइट",
- "color": "रंग",
- "addLink": "लिंक जोडा"
- },
- "tooltip": {
- "lightMode": "लाइट मोडमध्ये स्विच करा",
- "darkMode": "डार्क मोडमध्ये स्विच करा",
- "openAsPage": "पृष्ठ म्हणून उघडा",
- "addNewRow": "नवीन पंक्ती जोडा",
- "openMenu": "मेनू उघडण्यासाठी क्लिक करा",
- "dragRow": "पंक्तीचे स्थान बदलण्यासाठी ड्रॅग करा",
- "viewDataBase": "डेटाबेस पहा",
- "referencePage": "हे {name} संदर्भित आहे",
- "addBlockBelow": "खाली एक ब्लॉक जोडा",
- "aiGenerate": "निर्मिती करा"
- },
- "sideBar": {
- "closeSidebar": "साइडबार बंद करा",
- "openSidebar": "साइडबार उघडा",
- "expandSidebar": "पूर्ण पृष्ठावर विस्तारित करा",
- "personal": "वैयक्तिक",
- "private": "खाजगी",
- "workspace": "कार्यक्षेत्र",
- "favorites": "आवडती",
- "clickToHidePrivate": "खाजगी जागा लपवण्यासाठी क्लिक करा\nयेथे तयार केलेली पाने फक्त तुम्हाला दिसतील",
- "clickToHideWorkspace": "कार्यक्षेत्र लपवण्यासाठी क्लिक करा\nयेथे तयार केलेली पाने सर्व सदस्यांना दिसतील",
- "clickToHidePersonal": "वैयक्तिक जागा लपवण्यासाठी क्लिक करा",
- "clickToHideFavorites": "आवडती जागा लपवण्यासाठी क्लिक करा",
- "addAPage": "नवीन पृष्ठ जोडा",
- "addAPageToPrivate": "खाजगी जागेत पृष्ठ जोडा",
- "addAPageToWorkspace": "कार्यक्षेत्रात पृष्ठ जोडा",
- "recent": "अलीकडील",
- "today": "आज",
- "thisWeek": "या आठवड्यात",
- "others": "पूर्वीच्या आवडती",
- "earlier": "पूर्वीचे",
- "justNow": "आत्ताच",
- "minutesAgo": "{count} मिनिटांपूर्वी",
- "lastViewed": "शेवटी पाहिलेले",
- "favoriteAt": "आवडते म्हणून चिन्हांकित",
- "emptyRecent": "अलीकडील पृष्ठे नाहीत",
- "emptyRecentDescription": "तुम्ही पाहिलेली पृष्ठे येथे सहज पुन्हा मिळवण्यासाठी दिसतील.",
- "emptyFavorite": "आवडती पृष्ठे नाहीत",
- "emptyFavoriteDescription": "पृष्ठांना आवडते म्हणून चिन्हांकित करा—ते येथे झपाट्याने प्रवेशासाठी दिसतील!",
- "removePageFromRecent": "हे पृष्ठ अलीकडील यादीतून काढायचे?",
- "removeSuccess": "यशस्वीरित्या काढले गेले",
- "favoriteSpace": "आवडती",
- "RecentSpace": "अलीकडील",
- "Spaces": "जागा",
- "upgradeToPro": "Pro मध्ये अपग्रेड करा",
- "upgradeToAIMax": "अमर्यादित AI अनलॉक करा",
- "storageLimitDialogTitle": "तुमचा मोफत स्टोरेज संपला आहे. अमर्यादित स्टोरेजसाठी अपग्रेड करा",
- "storageLimitDialogTitleIOS": "तुमचा मोफत स्टोरेज संपला आहे.",
- "aiResponseLimitTitle": "तुमचे मोफत AI प्रतिसाद संपले आहेत. कृपया Pro प्लॅनला अपग्रेड करा किंवा AI add-on खरेदी करा",
- "aiResponseLimitDialogTitle": "AI प्रतिसाद मर्यादा गाठली आहे",
- "aiResponseLimit": "तुमचे मोफत AI प्रतिसाद संपले आहेत.\n\nसेटिंग्ज -> प्लॅन -> AI Max किंवा Pro प्लॅन क्लिक करा",
- "askOwnerToUpgradeToPro": "तुमच्या कार्यक्षेत्राचे मोफत स्टोरेज संपत चालले आहे. कृपया कार्यक्षेत्र मालकाला Pro प्लॅनमध्ये अपग्रेड करण्यास सांगा",
- "askOwnerToUpgradeToProIOS": "तुमच्या कार्यक्षेत्राचे मोफत स्टोरेज संपत चालले आहे.",
- "askOwnerToUpgradeToAIMax": "तुमच्या कार्यक्षेत्राचे मोफत AI प्रतिसाद संपले आहेत. कृपया कार्यक्षेत्र मालकाला प्लॅन अपग्रेड किंवा AI add-ons खरेदी करण्यास सांगा",
- "askOwnerToUpgradeToAIMaxIOS": "तुमच्या कार्यक्षेत्राचे मोफत AI प्रतिसाद संपले आहेत.",
- "purchaseAIMax": "तुमच्या कार्यक्षेत्राचे AI प्रतिमा प्रतिसाद संपले आहेत. कृपया कार्यक्षेत्र मालकाला AI Max खरेदी करण्यास सांगा",
- "aiImageResponseLimit": "तुमचे AI प्रतिमा प्रतिसाद संपले आहेत.\n\nसेटिंग्ज -> प्लॅन -> AI Max क्लिक करा",
- "purchaseStorageSpace": "स्टोरेज स्पेस खरेदी करा",
- "singleFileProPlanLimitationDescription": "तुम्ही मोफत प्लॅनमध्ये परवानगी दिलेल्या फाइल अपलोड आकाराची मर्यादा ओलांडली आहे. कृपया Pro प्लॅनमध्ये अपग्रेड करा",
- "purchaseAIResponse": "AI प्रतिसाद खरेदी करा",
- "askOwnerToUpgradeToLocalAI": "कार्यक्षेत्र मालकाला ऑन-डिव्हाइस AI सक्षम करण्यास सांगा",
- "upgradeToAILocal": "अत्याधिक गोपनीयतेसाठी तुमच्या डिव्हाइसवर लोकल मॉडेल चालवा",
- "upgradeToAILocalDesc": "PDFs सोबत गप्पा मारा, तुमचे लेखन सुधारा आणि लोकल AI वापरून टेबल्स आपोआप भरा"
-},
- "notifications": {
- "export": {
- "markdown": "टीप Markdown मध्ये निर्यात केली",
- "path": "Documents/flowy"
- }
- },
- "contactsPage": {
- "title": "संपर्क",
- "whatsHappening": "या आठवड्यात काय घडत आहे?",
- "addContact": "संपर्क जोडा",
- "editContact": "संपर्क संपादित करा"
- },
- "button": {
- "ok": "ठीक आहे",
- "confirm": "खात्री करा",
- "done": "पूर्ण",
- "cancel": "रद्द करा",
- "signIn": "साइन इन",
- "signOut": "साइन आउट",
- "complete": "पूर्ण करा",
- "save": "जतन करा",
- "generate": "निर्माण करा",
- "esc": "ESC",
- "keep": "ठेवा",
- "tryAgain": "पुन्हा प्रयत्न करा",
- "discard": "टाका",
- "replace": "बदला",
- "insertBelow": "खाली घाला",
- "insertAbove": "वर घाला",
- "upload": "अपलोड करा",
- "edit": "संपादित करा",
- "delete": "हटवा",
- "copy": "कॉपी करा",
- "duplicate": "प्रत बनवा",
- "putback": "परत ठेवा",
- "update": "अद्यतनित करा",
- "share": "शेअर करा",
- "removeFromFavorites": "आवडतीतून काढा",
- "removeFromRecent": "अलीकडील यादीतून काढा",
- "addToFavorites": "आवडतीत जोडा",
- "favoriteSuccessfully": "आवडतीत यशस्वीरित्या जोडले",
- "unfavoriteSuccessfully": "आवडतीतून यशस्वीरित्या काढले",
- "duplicateSuccessfully": "प्रत यशस्वीरित्या तयार झाली",
- "rename": "नाव बदला",
- "helpCenter": "मदत केंद्र",
- "add": "जोड़ा",
- "yes": "होय",
- "no": "नाही",
- "clear": "साफ करा",
- "remove": "काढा",
- "dontRemove": "काढू नका",
- "copyLink": "लिंक कॉपी करा",
- "align": "जुळवा",
- "login": "लॉगिन",
- "logout": "लॉगआउट",
- "deleteAccount": "खाते हटवा",
- "back": "मागे",
- "signInGoogle": "Google सह पुढे जा",
- "signInGithub": "GitHub सह पुढे जा",
- "signInDiscord": "Discord सह पुढे जा",
- "more": "अधिक",
- "create": "तयार करा",
- "close": "बंद करा",
- "next": "पुढे",
- "previous": "मागील",
- "submit": "सबमिट करा",
- "download": "डाउनलोड करा",
- "backToHome": "मुख्यपृष्ठावर परत जा",
- "viewing": "पाहत आहात",
- "editing": "संपादन करत आहात",
- "gotIt": "समजले",
- "retry": "पुन्हा प्रयत्न करा",
- "uploadFailed": "अपलोड अयशस्वी.",
- "copyLinkOriginal": "मूळ दुव्याची कॉपी करा"
- },
- "label": {
- "welcome": "स्वागत आहे!",
- "firstName": "पहिले नाव",
- "middleName": "मधले नाव",
- "lastName": "आडनाव",
- "stepX": "पायरी {X}"
- },
- "oAuth": {
- "err": {
- "failedTitle": "तुमच्या खात्याशी कनेक्ट होता आले नाही.",
- "failedMsg": "कृपया खात्री करा की तुम्ही ब्राउझरमध्ये साइन-इन प्रक्रिया पूर्ण केली आहे."
- },
- "google": {
- "title": "GOOGLE साइन-इन",
- "instruction1": "तुमचे Google Contacts आयात करण्यासाठी, तुम्हाला तुमच्या वेब ब्राउझरचा वापर करून या अॅप्लिकेशनला अधिकृत करणे आवश्यक आहे.",
- "instruction2": "ही कोड आयकॉनवर क्लिक करून किंवा मजकूर निवडून क्लिपबोर्डवर कॉपी करा:",
- "instruction3": "तुमच्या वेब ब्राउझरमध्ये खालील दुव्यावर जा आणि वरील कोड टाका:",
- "instruction4": "साइनअप पूर्ण झाल्यावर खालील बटण क्लिक करा:"
- }
- },
- "settings": {
- "title": "सेटिंग्ज",
- "popupMenuItem": {
- "settings": "सेटिंग्ज",
- "members": "सदस्य",
- "trash": "कचरा",
- "helpAndSupport": "मदत आणि समर्थन"
- },
- "sites": {
- "title": "साइट्स",
- "namespaceTitle": "नेमस्पेस",
- "namespaceDescription": "तुमचा नेमस्पेस आणि मुख्यपृष्ठ व्यवस्थापित करा",
- "namespaceHeader": "नेमस्पेस",
- "homepageHeader": "मुख्यपृष्ठ",
- "updateNamespace": "नेमस्पेस अद्यतनित करा",
- "removeHomepage": "मुख्यपृष्ठ हटवा",
- "selectHomePage": "एक पृष्ठ निवडा",
- "clearHomePage": "या नेमस्पेससाठी मुख्यपृष्ठ साफ करा",
- "customUrl": "स्वतःची URL",
- "namespace": {
- "description": "हे बदल सर्व प्रकाशित पृष्ठांवर लागू होतील जे या नेमस्पेसवर चालू आहेत",
- "tooltip": "कोणताही अनुचित नेमस्पेस आम्ही काढून टाकण्याचा अधिकार राखून ठेवतो",
- "updateExistingNamespace": "विद्यमान नेमस्पेस अद्यतनित करा",
- "upgradeToPro": "मुख्यपृष्ठ सेट करण्यासाठी Pro प्लॅनमध्ये अपग्रेड करा",
- "redirectToPayment": "पेमेंट पृष्ठावर वळवत आहे...",
- "onlyWorkspaceOwnerCanSetHomePage": "फक्त कार्यक्षेत्र मालकच मुख्यपृष्ठ सेट करू शकतो",
- "pleaseAskOwnerToSetHomePage": "कृपया कार्यक्षेत्र मालकाला Pro प्लॅनमध्ये अपग्रेड करण्यास सांगा"
- },
- "publishedPage": {
- "title": "सर्व प्रकाशित पृष्ठे",
- "description": "तुमची प्रकाशित पृष्ठे व्यवस्थापित करा",
- "page": "पृष्ठ",
- "pathName": "पथाचे नाव",
- "date": "प्रकाशन तारीख",
- "emptyHinText": "या कार्यक्षेत्रात तुमच्याकडे कोणतीही प्रकाशित पृष्ठे नाहीत",
- "noPublishedPages": "प्रकाशित पृष्ठे नाहीत",
- "settings": "प्रकाशन सेटिंग्ज",
- "clickToOpenPageInApp": "पृष्ठ अॅपमध्ये उघडा",
- "clickToOpenPageInBrowser": "पृष्ठ ब्राउझरमध्ये उघडा"
- }
- }
- },
- "error": {
- "failedToGeneratePaymentLink": "Pro प्लॅनसाठी पेमेंट लिंक तयार करण्यात अयशस्वी",
- "failedToUpdateNamespace": "नेमस्पेस अद्यतनित करण्यात अयशस्वी",
- "proPlanLimitation": "नेमस्पेस अद्यतनित करण्यासाठी तुम्हाला Pro प्लॅनमध्ये अपग्रेड करणे आवश्यक आहे",
- "namespaceAlreadyInUse": "नेमस्पेस आधीच वापरात आहे, कृपया दुसरे प्रयत्न करा",
- "invalidNamespace": "अवैध नेमस्पेस, कृपया दुसरे प्रयत्न करा",
- "namespaceLengthAtLeast2Characters": "नेमस्पेस किमान २ अक्षरे लांब असावे",
- "onlyWorkspaceOwnerCanUpdateNamespace": "फक्त कार्यक्षेत्र मालकच नेमस्पेस अद्यतनित करू शकतो",
- "onlyWorkspaceOwnerCanRemoveHomepage": "फक्त कार्यक्षेत्र मालकच मुख्यपृष्ठ हटवू शकतो",
- "setHomepageFailed": "मुख्यपृष्ठ सेट करण्यात अयशस्वी",
- "namespaceTooLong": "नेमस्पेस खूप लांब आहे, कृपया दुसरे प्रयत्न करा",
- "namespaceTooShort": "नेमस्पेस खूप लहान आहे, कृपया दुसरे प्रयत्न करा",
- "namespaceIsReserved": "हा नेमस्पेस राखीव आहे, कृपया दुसरे प्रयत्न करा",
- "updatePathNameFailed": "पथाचे नाव अद्यतनित करण्यात अयशस्वी",
- "removeHomePageFailed": "मुख्यपृष्ठ हटवण्यात अयशस्वी",
- "publishNameContainsInvalidCharacters": "पथाच्या नावामध्ये अवैध अक्षरे आहेत, कृपया दुसरे प्रयत्न करा",
- "publishNameTooShort": "पथाचे नाव खूप लहान आहे, कृपया दुसरे प्रयत्न करा",
- "publishNameTooLong": "पथाचे नाव खूप लांब आहे, कृपया दुसरे प्रयत्न करा",
- "publishNameAlreadyInUse": "हे पथाचे नाव आधीच वापरले गेले आहे, कृपया दुसरे प्रयत्न करा",
- "namespaceContainsInvalidCharacters": "नेमस्पेसमध्ये अवैध अक्षरे आहेत, कृपया दुसरे प्रयत्न करा",
- "publishPermissionDenied": "फक्त कार्यक्षेत्र मालक किंवा पृष्ठ प्रकाशकच प्रकाशन सेटिंग्ज व्यवस्थापित करू शकतो",
- "publishNameCannotBeEmpty": "पथाचे नाव रिकामे असू शकत नाही, कृपया दुसरे प्रयत्न करा"
- },
- "success": {
- "namespaceUpdated": "नेमस्पेस यशस्वीरित्या अद्यतनित केला",
- "setHomepageSuccess": "मुख्यपृष्ठ यशस्वीरित्या सेट केले",
- "updatePathNameSuccess": "पथाचे नाव यशस्वीरित्या अद्यतनित केले",
- "removeHomePageSuccess": "मुख्यपृष्ठ यशस्वीरित्या हटवले"
- },
- "accountPage": {
- "menuLabel": "खाते आणि अॅप",
- "title": "माझे खाते",
- "general": {
- "title": "खात्याचे नाव आणि प्रोफाइल प्रतिमा",
- "changeProfilePicture": "प्रोफाइल प्रतिमा बदला"
- },
- "email": {
- "title": "ईमेल",
- "actions": {
- "change": "ईमेल बदला"
- }
- },
- "login": {
- "title": "खाते लॉगिन",
- "loginLabel": "लॉगिन",
- "logoutLabel": "लॉगआउट"
- },
- "isUpToDate": "@:appName अद्ययावत आहे!",
- "officialVersion": "आवृत्ती {version} (अधिकृत बिल्ड)"
-},
- "workspacePage": {
- "menuLabel": "कार्यक्षेत्र",
- "title": "कार्यक्षेत्र",
- "description": "तुमचे कार्यक्षेत्र स्वरूप, थीम, फॉन्ट, मजकूर रचना, दिनांक/वेळ फॉरमॅट आणि भाषा सानुकूलित करा.",
- "workspaceName": {
- "title": "कार्यक्षेत्राचे नाव"
- },
- "workspaceIcon": {
- "title": "कार्यक्षेत्राचे चिन्ह",
- "description": "तुमच्या कार्यक्षेत्रासाठी प्रतिमा अपलोड करा किंवा इमोजी वापरा. हे चिन्ह साइडबार आणि सूचना मध्ये दिसेल."
- },
- "appearance": {
- "title": "दृश्यरूप",
- "description": "कार्यक्षेत्राचे दृश्यरूप, थीम, फॉन्ट, मजकूर रचना, दिनांक, वेळ आणि भाषा सानुकूलित करा.",
- "options": {
- "system": "स्वयंचलित",
- "light": "लाइट",
- "dark": "डार्क"
- }
- }
- },
- "resetCursorColor": {
- "title": "दस्तऐवज कर्सरचा रंग रीसेट करा",
- "description": "तुम्हाला कर्सरचा रंग रीसेट करायचा आहे का?"
- },
- "resetSelectionColor": {
- "title": "दस्तऐवज निवडीचा रंग रीसेट करा",
- "description": "तुम्हाला निवडीचा रंग रीसेट करायचा आहे का?"
- },
- "resetWidth": {
- "resetSuccess": "दस्तऐवजाची रुंदी यशस्वीरित्या रीसेट केली"
- },
- "theme": {
- "title": "थीम",
- "description": "पूर्व-निर्धारित थीम निवडा किंवा तुमची स्वतःची थीम अपलोड करा.",
- "uploadCustomThemeTooltip": "स्वतःची थीम अपलोड करा"
- },
- "workspaceFont": {
- "title": "कार्यक्षेत्र फॉन्ट",
- "noFontHint": "कोणताही फॉन्ट सापडला नाही, कृपया दुसरा शब्द वापरून पहा."
- },
- "textDirection": {
- "title": "मजकूर दिशा",
- "leftToRight": "डावीकडून उजवीकडे",
- "rightToLeft": "उजवीकडून डावीकडे",
- "auto": "स्वयंचलित",
- "enableRTLItems": "RTL टूलबार घटक सक्षम करा"
- },
- "layoutDirection": {
- "title": "लेआउट दिशा",
- "leftToRight": "डावीकडून उजवीकडे",
- "rightToLeft": "उजवीकडून डावीकडे"
- },
- "dateTime": {
- "title": "दिनांक आणि वेळ",
- "example": "{} वाजता {} ({})",
- "24HourTime": "२४-तास वेळ",
- "dateFormat": {
- "label": "दिनांक फॉरमॅट",
- "local": "स्थानिक",
- "us": "US",
- "iso": "ISO",
- "friendly": "सुलभ",
- "dmy": "D/M/Y"
- }
- },
- "language": {
- "title": "भाषा"
- },
- "deleteWorkspacePrompt": {
- "title": "कार्यक्षेत्र हटवा",
- "content": "तुम्हाला हे कार्यक्षेत्र हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही, आणि तुम्ही प्रकाशित केलेली कोणतीही पृष्ठे अप्रकाशित होतील."
- },
- "leaveWorkspacePrompt": {
- "title": "कार्यक्षेत्र सोडा",
- "content": "तुम्हाला हे कार्यक्षेत्र सोडायचे आहे का? यानंतर तुम्हाला यामधील सर्व पृष्ठे आणि डेटावर प्रवेश मिळणार नाही.",
- "success": "तुम्ही कार्यक्षेत्र यशस्वीरित्या सोडले.",
- "fail": "कार्यक्षेत्र सोडण्यात अयशस्वी."
- },
- "manageWorkspace": {
- "title": "कार्यक्षेत्र व्यवस्थापित करा",
- "leaveWorkspace": "कार्यक्षेत्र सोडा",
- "deleteWorkspace": "कार्यक्षेत्र हटवा"
- },
- "manageDataPage": {
- "menuLabel": "डेटा व्यवस्थापित करा",
- "title": "डेटा व्यवस्थापन",
- "description": "@:appName मध्ये स्थानिक डेटा संचयन व्यवस्थापित करा किंवा तुमचा विद्यमान डेटा आयात करा.",
- "dataStorage": {
- "title": "फाइल संचयन स्थान",
- "tooltip": "जिथे तुमच्या फाइल्स संग्रहित आहेत ते स्थान",
- "actions": {
- "change": "मार्ग बदला",
- "open": "फोल्डर उघडा",
- "openTooltip": "सध्याच्या डेटा फोल्डरचे स्थान उघडा",
- "copy": "मार्ग कॉपी करा",
- "copiedHint": "मार्ग कॉपी केला!",
- "resetTooltip": "मूलभूत स्थानावर रीसेट करा"
- },
- "resetDialog": {
- "title": "तुम्हाला खात्री आहे का?",
- "description": "पथ मूलभूत डेटा स्थानावर रीसेट केल्याने तुमचा डेटा हटवला जाणार नाही. तुम्हाला सध्याचा डेटा पुन्हा आयात करायचा असल्यास, कृपया आधी त्याचा पथ कॉपी करा."
- }
- },
- "importData": {
- "title": "डेटा आयात करा",
- "tooltip": "@:appName बॅकअप/डेटा फोल्डरमधून डेटा आयात करा",
- "description": "बाह्य @:appName डेटा फोल्डरमधून डेटा कॉपी करा",
- "action": "फाइल निवडा"
- },
- "encryption": {
- "title": "एनक्रिप्शन",
- "tooltip": "तुमचा डेटा कसा संग्रहित आणि एनक्रिप्ट केला जातो ते व्यवस्थापित करा",
- "descriptionNoEncryption": "एनक्रिप्शन चालू केल्याने सर्व डेटा एनक्रिप्ट केला जाईल. ही क्रिया पूर्ववत केली जाऊ शकत नाही.",
- "descriptionEncrypted": "तुमचा डेटा एनक्रिप्टेड आहे.",
- "action": "डेटा एनक्रिप्ट करा",
- "dialog": {
- "title": "संपूर्ण डेटा एनक्रिप्ट करायचा?",
- "description": "तुमचा संपूर्ण डेटा एनक्रिप्ट केल्याने तो सुरक्षित राहील. ही क्रिया पूर्ववत केली जाऊ शकत नाही. तुम्हाला पुढे जायचे आहे का?"
- }
- },
- "cache": {
- "title": "कॅशे साफ करा",
- "description": "प्रतिमा न दिसणे, पृष्ठे हरवणे, फॉन्ट लोड न होणे अशा समस्यांचे निराकरण करण्यासाठी मदत करा. याचा तुमच्या डेटावर परिणाम होणार नाही.",
- "dialog": {
- "title": "कॅशे साफ करा",
- "description": "प्रतिमा न दिसणे, पृष्ठे हरवणे, फॉन्ट लोड न होणे अशा समस्यांचे निराकरण करण्यासाठी मदत करा. याचा तुमच्या डेटावर परिणाम होणार नाही.",
- "successHint": "कॅशे साफ झाली!"
- }
- },
- "data": {
- "fixYourData": "तुमचा डेटा सुधारा",
- "fixButton": "सुधारा",
- "fixYourDataDescription": "तुमच्या डेटामध्ये काही अडचण येत असल्यास, तुम्ही येथे ती सुधारू शकता."
- }
- },
- "shortcutsPage": {
- "menuLabel": "शॉर्टकट्स",
- "title": "शॉर्टकट्स",
- "editBindingHint": "नवीन बाइंडिंग टाका",
- "searchHint": "शोधा",
- "actions": {
- "resetDefault": "मूलभूत रीसेट करा"
- },
- "errorPage": {
- "message": "शॉर्टकट्स लोड करण्यात अयशस्वी: {}",
- "howToFix": "कृपया पुन्हा प्रयत्न करा. समस्या कायम राहिल्यास GitHub वर संपर्क साधा."
- },
- "resetDialog": {
- "title": "शॉर्टकट्स रीसेट करा",
- "description": "हे सर्व कीबाइंडिंग्जना मूळ स्थितीत रीसेट करेल. ही क्रिया पूर्ववत करता येणार नाही. तुम्हाला खात्री आहे का?",
- "buttonLabel": "रीसेट करा"
- },
- "conflictDialog": {
- "title": "{} आधीच वापरले जात आहे",
- "descriptionPrefix": "हे कीबाइंडिंग सध्या ",
- "descriptionSuffix": " यामध्ये वापरले जात आहे. तुम्ही हे कीबाइंडिंग बदलल्यास, ते {} मधून काढले जाईल.",
- "confirmLabel": "पुढे जा"
- },
- "editTooltip": "कीबाइंडिंग संपादित करण्यासाठी क्लिक करा",
- "keybindings": {
- "toggleToDoList": "टू-डू सूची चालू/बंद करा",
- "insertNewParagraphInCodeblock": "नवीन परिच्छेद टाका",
- "pasteInCodeblock": "कोडब्लॉकमध्ये पेस्ट करा",
- "selectAllCodeblock": "सर्व निवडा",
- "indentLineCodeblock": "ओळीच्या सुरुवातीला दोन स्पेस टाका",
- "outdentLineCodeblock": "ओळीच्या सुरुवातीची दोन स्पेस काढा",
- "twoSpacesCursorCodeblock": "कर्सरवर दोन स्पेस टाका",
- "copy": "निवड कॉपी करा",
- "paste": "मजकुरात पेस्ट करा",
- "cut": "निवड कट करा",
- "alignLeft": "मजकूर डावीकडे संरेखित करा",
- "alignCenter": "मजकूर मधोमध संरेखित करा",
- "alignRight": "मजकूर उजवीकडे संरेखित करा",
- "insertInlineMathEquation": "इनलाइन गणितीय सूत्र टाका",
- "undo": "पूर्ववत करा",
- "redo": "पुन्हा करा",
- "convertToParagraph": "ब्लॉक परिच्छेदात रूपांतरित करा",
- "backspace": "हटवा",
- "deleteLeftWord": "डावीकडील शब्द हटवा",
- "deleteLeftSentence": "डावीकडील वाक्य हटवा",
- "delete": "उजवीकडील अक्षर हटवा",
- "deleteMacOS": "डावीकडील अक्षर हटवा",
- "deleteRightWord": "उजवीकडील शब्द हटवा",
- "moveCursorLeft": "कर्सर डावीकडे हलवा",
- "moveCursorBeginning": "कर्सर सुरुवातीला हलवा",
- "moveCursorLeftWord": "कर्सर एक शब्द डावीकडे हलवा",
- "moveCursorLeftSelect": "निवडा आणि कर्सर डावीकडे हलवा",
- "moveCursorBeginSelect": "निवडा आणि कर्सर सुरुवातीला हलवा",
- "moveCursorLeftWordSelect": "निवडा आणि कर्सर एक शब्द डावीकडे हलवा",
- "moveCursorRight": "कर्सर उजवीकडे हलवा",
- "moveCursorEnd": "कर्सर शेवटी हलवा",
- "moveCursorRightWord": "कर्सर एक शब्द उजवीकडे हलवा",
- "moveCursorRightSelect": "निवडा आणि कर्सर उजवीकडे हलवा",
- "moveCursorEndSelect": "निवडा आणि कर्सर शेवटी हलवा",
- "moveCursorRightWordSelect": "निवडा आणि कर्सर एक शब्द उजवीकडे हलवा",
- "moveCursorUp": "कर्सर वर हलवा",
- "moveCursorTopSelect": "निवडा आणि कर्सर वर हलवा",
- "moveCursorTop": "कर्सर वर हलवा",
- "moveCursorUpSelect": "निवडा आणि कर्सर वर हलवा",
- "moveCursorBottomSelect": "निवडा आणि कर्सर खाली हलवा",
- "moveCursorBottom": "कर्सर खाली हलवा",
- "moveCursorDown": "कर्सर खाली हलवा",
- "moveCursorDownSelect": "निवडा आणि कर्सर खाली हलवा",
- "home": "वर स्क्रोल करा",
- "end": "खाली स्क्रोल करा",
- "toggleBold": "बोल्ड चालू/बंद करा",
- "toggleItalic": "इटालिक चालू/बंद करा",
- "toggleUnderline": "अधोरेखित चालू/बंद करा",
- "toggleStrikethrough": "स्ट्राईकथ्रू चालू/बंद करा",
- "toggleCode": "इनलाइन कोड चालू/बंद करा",
- "toggleHighlight": "हायलाईट चालू/बंद करा",
- "showLinkMenu": "लिंक मेनू दाखवा",
- "openInlineLink": "इनलाइन लिंक उघडा",
- "openLinks": "सर्व निवडलेले लिंक उघडा",
- "indent": "इंडेंट",
- "outdent": "आउटडेंट",
- "exit": "संपादनातून बाहेर पडा",
- "pageUp": "एक पृष्ठ वर स्क्रोल करा",
- "pageDown": "एक पृष्ठ खाली स्क्रोल करा",
- "selectAll": "सर्व निवडा",
- "pasteWithoutFormatting": "फॉरमॅटिंगशिवाय पेस्ट करा",
- "showEmojiPicker": "इमोजी निवडकर्ता दाखवा",
- "enterInTableCell": "टेबलमध्ये नवीन ओळ जोडा",
- "leftInTableCell": "टेबलमध्ये डावीकडील सेलमध्ये जा",
- "rightInTableCell": "टेबलमध्ये उजवीकडील सेलमध्ये जा",
- "upInTableCell": "टेबलमध्ये वरील सेलमध्ये जा",
- "downInTableCell": "टेबलमध्ये खालील सेलमध्ये जा",
- "tabInTableCell": "टेबलमध्ये पुढील सेलमध्ये जा",
- "shiftTabInTableCell": "टेबलमध्ये मागील सेलमध्ये जा",
- "backSpaceInTableCell": "सेलच्या सुरुवातीला थांबा"
- },
- "commands": {
- "codeBlockNewParagraph": "कोडब्लॉकच्या शेजारी नवीन परिच्छेद टाका",
- "codeBlockIndentLines": "कोडब्लॉकमध्ये ओळीच्या सुरुवातीला दोन स्पेस टाका",
- "codeBlockOutdentLines": "कोडब्लॉकमध्ये ओळीच्या सुरुवातीची दोन स्पेस काढा",
- "codeBlockAddTwoSpaces": "कोडब्लॉकमध्ये कर्सरच्या ठिकाणी दोन स्पेस टाका",
- "codeBlockSelectAll": "कोडब्लॉकमधील सर्व मजकूर निवडा",
- "codeBlockPasteText": "कोडब्लॉकमध्ये मजकूर पेस्ट करा",
- "textAlignLeft": "मजकूर डावीकडे संरेखित करा",
- "textAlignCenter": "मजकूर मधोमध संरेखित करा",
- "textAlignRight": "मजकूर उजवीकडे संरेखित करा"
- },
- "couldNotLoadErrorMsg": "शॉर्टकट लोड करता आले नाहीत. पुन्हा प्रयत्न करा",
- "couldNotSaveErrorMsg": "शॉर्टकट सेव्ह करता आले नाहीत. पुन्हा प्रयत्न करा"
-},
- "aiPage": {
- "title": "AI सेटिंग्ज",
- "menuLabel": "AI सेटिंग्ज",
- "keys": {
- "enableAISearchTitle": "AI शोध",
- "aiSettingsDescription": "AppFlowy AI चालवण्यासाठी तुमचा पसंतीचा मॉडेल निवडा. आता GPT-4o, GPT-o3-mini, DeepSeek R1, Claude 3.5 Sonnet आणि Ollama मधील मॉडेल्सचा समावेश आहे.",
- "loginToEnableAIFeature": "AI वैशिष्ट्ये फक्त @:appName Cloud मध्ये लॉगिन केल्यानंतरच सक्षम होतात. तुमच्याकडे @:appName खाते नसेल तर 'माय अकाउंट' मध्ये जाऊन साइन अप करा.",
- "llmModel": "भाषा मॉडेल",
- "llmModelType": "भाषा मॉडेल प्रकार",
- "downloadLLMPrompt": "{} डाउनलोड करा",
- "downloadAppFlowyOfflineAI": "AI ऑफलाइन पॅकेज डाउनलोड केल्याने तुमच्या डिव्हाइसवर AI चालवता येईल. तुम्हाला पुढे जायचे आहे का?",
- "downloadLLMPromptDetail": "{} स्थानिक मॉडेल डाउनलोड करण्यासाठी सुमारे {} संचयन लागेल. तुम्हाला पुढे जायचे आहे का?",
- "downloadBigFilePrompt": "डाउनलोड पूर्ण होण्यासाठी सुमारे १० मिनिटे लागू शकतात",
- "downloadAIModelButton": "डाउनलोड करा",
- "downloadingModel": "डाउनलोड करत आहे",
- "localAILoaded": "स्थानिक AI मॉडेल यशस्वीरित्या जोडले गेले आणि वापरण्यास सज्ज आहे",
- "localAIStart": "स्थानिक AI सुरू होत आहे. जर हळू वाटत असेल, तर ते बंद करून पुन्हा सुरू करून पहा",
- "localAILoading": "स्थानिक AI Chat मॉडेल लोड होत आहे...",
- "localAIStopped": "स्थानिक AI थांबले आहे",
- "localAIRunning": "स्थानिक AI चालू आहे",
- "localAINotReadyRetryLater": "स्थानिक AI सुरू होत आहे, कृपया नंतर पुन्हा प्रयत्न करा",
- "localAIDisabled": "तुम्ही स्थानिक AI वापरत आहात, पण ते अकार्यक्षम आहे. कृपया सेटिंग्जमध्ये जाऊन ते सक्षम करा किंवा दुसरे मॉडेल वापरून पहा",
- "localAIInitializing": "स्थानिक AI लोड होत आहे. तुमच्या डिव्हाइसवर अवलंबून याला काही मिनिटे लागू शकतात",
- "localAINotReadyTextFieldPrompt": "स्थानिक AI लोड होत असताना संपादन करता येणार नाही",
- "failToLoadLocalAI": "स्थानिक AI सुरू करण्यात अयशस्वी.",
- "restartLocalAI": "पुन्हा सुरू करा",
- "disableLocalAITitle": "स्थानिक AI अकार्यक्षम करा",
- "disableLocalAIDescription": "तुम्हाला स्थानिक AI अकार्यक्षम करायचा आहे का?",
- "localAIToggleTitle": "AppFlowy स्थानिक AI (LAI)",
- "localAIToggleSubTitle": "AppFlowy मध्ये अत्याधुनिक स्थानिक AI मॉडेल्स वापरून गोपनीयता आणि सुरक्षा सुनिश्चित करा",
- "offlineAIInstruction1": "हे अनुसरा",
- "offlineAIInstruction2": "सूचना",
- "offlineAIInstruction3": "ऑफलाइन AI सक्षम करण्यासाठी.",
- "offlineAIDownload1": "जर तुम्ही AppFlowy AI डाउनलोड केले नसेल, तर कृपया",
- "offlineAIDownload2": "डाउनलोड",
- "offlineAIDownload3": "करा",
- "activeOfflineAI": "सक्रिय",
- "downloadOfflineAI": "डाउनलोड करा",
- "openModelDirectory": "फोल्डर उघडा",
- "laiNotReady": "स्थानिक AI अॅप योग्य प्रकारे इन्स्टॉल झालेले नाही.",
- "ollamaNotReady": "Ollama सर्व्हर तयार नाही.",
- "pleaseFollowThese": "कृपया हे अनुसरा",
- "instructions": "सूचना",
- "installOllamaLai": "Ollama आणि AppFlowy स्थानिक AI सेटअप करण्यासाठी.",
- "modelsMissing": "आवश्यक मॉडेल्स सापडत नाहीत.",
- "downloadModel": "त्यांना डाउनलोड करण्यासाठी."
- }
-},
- "planPage": {
- "menuLabel": "योजना",
- "title": "दर योजना",
- "planUsage": {
- "title": "योजनेचा वापर सारांश",
- "storageLabel": "स्टोरेज",
- "storageUsage": "{} पैकी {} GB",
- "unlimitedStorageLabel": "अमर्यादित स्टोरेज",
- "collaboratorsLabel": "सदस्य",
- "collaboratorsUsage": "{} पैकी {}",
- "aiResponseLabel": "AI प्रतिसाद",
- "aiResponseUsage": "{} पैकी {}",
- "unlimitedAILabel": "अमर्यादित AI प्रतिसाद",
- "proBadge": "प्रो",
- "aiMaxBadge": "AI Max",
- "aiOnDeviceBadge": "मॅकसाठी ऑन-डिव्हाइस AI",
- "memberProToggle": "अधिक सदस्य आणि अमर्यादित AI",
- "aiMaxToggle": "अमर्यादित AI आणि प्रगत मॉडेल्सचा प्रवेश",
- "aiOnDeviceToggle": "जास्त गोपनीयतेसाठी स्थानिक AI",
- "aiCredit": {
- "title": "@:appName AI क्रेडिट जोडा",
- "price": "{}",
- "priceDescription": "1,000 क्रेडिट्ससाठी",
- "purchase": "AI खरेदी करा",
- "info": "प्रत्येक कार्यक्षेत्रासाठी 1,000 AI क्रेडिट्स जोडा आणि आपल्या कामाच्या प्रवाहात सानुकूलनीय AI सहजपणे एकत्र करा — अधिक स्मार्ट आणि जलद निकालांसाठी:",
- "infoItemOne": "प्रत्येक डेटाबेससाठी 10,000 प्रतिसाद",
- "infoItemTwo": "प्रत्येक कार्यक्षेत्रासाठी 1,000 प्रतिसाद"
- },
- "currentPlan": {
- "bannerLabel": "सद्य योजना",
- "freeTitle": "फ्री",
- "proTitle": "प्रो",
- "teamTitle": "टीम",
- "freeInfo": "2 सदस्यांपर्यंत वैयक्तिक वापरासाठी उत्तम",
- "proInfo": "10 सदस्यांपर्यंत लहान आणि मध्यम टीमसाठी योग्य",
- "teamInfo": "सर्व उत्पादनक्षम आणि सुव्यवस्थित टीमसाठी योग्य",
- "upgrade": "योजना बदला",
- "canceledInfo": "तुमची योजना रद्द करण्यात आली आहे, {} रोजी तुम्हाला फ्री योजनेवर डाऊनग्रेड केले जाईल."
- },
- "addons": {
- "title": "ऍड-ऑन्स",
- "addLabel": "जोडा",
- "activeLabel": "जोडले गेले",
- "aiMax": {
- "title": "AI Max",
- "description": "प्रगत AI मॉडेल्ससह अमर्यादित AI प्रतिसाद आणि दरमहा 50 AI प्रतिमा",
- "price": "{}",
- "priceInfo": "प्रति वापरकर्ता प्रति महिना (वार्षिक बिलिंग)"
- },
- "aiOnDevice": {
- "title": "मॅकसाठी ऑन-डिव्हाइस AI",
- "description": "तुमच्या डिव्हाइसवर Mistral 7B, LLAMA 3 आणि अधिक स्थानिक मॉडेल्स चालवा",
- "price": "{}",
- "priceInfo": "प्रति वापरकर्ता प्रति महिना (वार्षिक बिलिंग)",
- "recommend": "M1 किंवा नवीनतम शिफारस केली जाते"
- }
- },
- "deal": {
- "bannerLabel": "नववर्षाचे विशेष ऑफर!",
- "title": "तुमची टीम वाढवा!",
- "info": "Pro आणि Team योजनांवर 10% सूट मिळवा! @:appName AI सह शक्तिशाली नवीन वैशिष्ट्यांसह तुमचे कार्यक्षेत्र अधिक कार्यक्षम बनवा.",
- "viewPlans": "योजना पहा"
- }
- }
-},
- "billingPage": {
- "menuLabel": "बिलिंग",
- "title": "बिलिंग",
- "plan": {
- "title": "योजना",
- "freeLabel": "फ्री",
- "proLabel": "प्रो",
- "planButtonLabel": "योजना बदला",
- "billingPeriod": "बिलिंग कालावधी",
- "periodButtonLabel": "कालावधी संपादित करा"
- },
- "paymentDetails": {
- "title": "पेमेंट तपशील",
- "methodLabel": "पेमेंट पद्धत",
- "methodButtonLabel": "पद्धत संपादित करा"
- },
- "addons": {
- "title": "ऍड-ऑन्स",
- "addLabel": "जोडा",
- "removeLabel": "काढा",
- "renewLabel": "नवीन करा",
- "aiMax": {
- "label": "AI Max",
- "description": "अमर्यादित AI आणि प्रगत मॉडेल्स अनलॉक करा",
- "activeDescription": "पुढील बिलिंग तारीख {} आहे",
- "canceledDescription": "AI Max {} पर्यंत उपलब्ध असेल"
- },
- "aiOnDevice": {
- "label": "मॅकसाठी ऑन-डिव्हाइस AI",
- "description": "तुमच्या डिव्हाइसवर अमर्यादित ऑन-डिव्हाइस AI अनलॉक करा",
- "activeDescription": "पुढील बिलिंग तारीख {} आहे",
- "canceledDescription": "मॅकसाठी ऑन-डिव्हाइस AI {} पर्यंत उपलब्ध असेल"
- },
- "removeDialog": {
- "title": "{} काढा",
- "description": "तुम्हाला {plan} काढायचे आहे का? तुम्हाला तत्काळ {plan} चे सर्व फिचर्स आणि फायदे वापरण्याचा अधिकार गमावावा लागेल."
- }
- },
- "currentPeriodBadge": "सद्य कालावधी",
- "changePeriod": "कालावधी बदला",
- "planPeriod": "{} कालावधी",
- "monthlyInterval": "मासिक",
- "monthlyPriceInfo": "प्रति सदस्य, मासिक बिलिंग",
- "annualInterval": "वार्षिक",
- "annualPriceInfo": "प्रति सदस्य, वार्षिक बिलिंग"
-},
- "comparePlanDialog": {
- "title": "योजना तुलना आणि निवड",
- "planFeatures": "योजनेची\nवैशिष्ट्ये",
- "current": "सध्याची",
- "actions": {
- "upgrade": "अपग्रेड करा",
- "downgrade": "डाऊनग्रेड करा",
- "current": "सध्याची"
- },
- "freePlan": {
- "title": "फ्री",
- "description": "२ सदस्यांपर्यंत व्यक्तींसाठी सर्व काही व्यवस्थित करण्यासाठी",
- "price": "{}",
- "priceInfo": "सदैव फ्री"
- },
- "proPlan": {
- "title": "प्रो",
- "description": "लहान टीम्ससाठी प्रोजेक्ट्स व ज्ञान व्यवस्थापनासाठी",
- "price": "{}",
- "priceInfo": "प्रति सदस्य प्रति महिना\nवार्षिक बिलिंग\n\n{} मासिक बिलिंगसाठी"
- },
- "planLabels": {
- "itemOne": "वर्कस्पेसेस",
- "itemTwo": "सदस्य",
- "itemThree": "स्टोरेज",
- "itemFour": "रिअल-टाइम सहकार्य",
- "itemFive": "मोबाईल अॅप",
- "itemSix": "AI प्रतिसाद",
- "itemSeven": "AI प्रतिमा",
- "itemFileUpload": "फाइल अपलोड",
- "customNamespace": "सानुकूल नेमस्पेस",
- "tooltipSix": "‘Lifetime’ म्हणजे या प्रतिसादांची मर्यादा कधीही रीसेट केली जात नाही",
- "intelligentSearch": "स्मार्ट शोध",
- "tooltipSeven": "तुमच्या कार्यक्षेत्रासाठी URL चा भाग सानुकूलित करू देते",
- "customNamespaceTooltip": "सानुकूल प्रकाशित साइट URL"
- },
- "freeLabels": {
- "itemOne": "प्रत्येक वर्कस्पेसवर शुल्क",
- "itemTwo": "२ पर्यंत",
- "itemThree": "५ GB",
- "itemFour": "होय",
- "itemFive": "होय",
- "itemSix": "१० कायमस्वरूपी",
- "itemSeven": "२ कायमस्वरूपी",
- "itemFileUpload": "७ MB पर्यंत",
- "intelligentSearch": "स्मार्ट शोध"
- },
- "proLabels": {
- "itemOne": "प्रत्येक वर्कस्पेसवर शुल्क",
- "itemTwo": "१० पर्यंत",
- "itemThree": "अमर्यादित",
- "itemFour": "होय",
- "itemFive": "होय",
- "itemSix": "अमर्यादित",
- "itemSeven": "दर महिन्याला १० प्रतिमा",
- "itemFileUpload": "अमर्यादित",
- "intelligentSearch": "स्मार्ट शोध"
- },
- "paymentSuccess": {
- "title": "तुम्ही आता {} योजनेवर आहात!",
- "description": "तुमचे पेमेंट यशस्वीरित्या पूर्ण झाले असून तुमची योजना @:appName {} मध्ये अपग्रेड झाली आहे. तुम्ही 'योजना' पृष्ठावर तपशील पाहू शकता."
- },
- "downgradeDialog": {
- "title": "तुम्हाला योजना डाऊनग्रेड करायची आहे का?",
- "description": "तुमची योजना डाऊनग्रेड केल्यास तुम्ही फ्री योजनेवर परत जाल. काही सदस्यांना या कार्यक्षेत्राचा प्रवेश मिळणार नाही आणि तुम्हाला स्टोरेज मर्यादेप्रमाणे जागा रिकामी करावी लागू शकते.",
- "downgradeLabel": "योजना डाऊनग्रेड करा"
- }
-},
- "cancelSurveyDialog": {
- "title": "तुम्ही जात आहात याचे दुःख आहे",
- "description": "तुम्ही जात आहात याचे आम्हाला खरोखरच दुःख वाटते. @:appName सुधारण्यासाठी तुमचा अभिप्राय आम्हाला महत्त्वाचा वाटतो. कृपया काही क्षण घेऊन काही प्रश्नांची उत्तरे द्या.",
- "commonOther": "इतर",
- "otherHint": "तुमचे उत्तर येथे लिहा",
- "questionOne": {
- "question": "तुम्ही तुमची @:appName Pro सदस्यता का रद्द केली?",
- "answerOne": "खर्च खूप जास्त आहे",
- "answerTwo": "वैशिष्ट्ये अपेक्षांनुसार नव्हती",
- "answerThree": "यापेक्षा चांगला पर्याय सापडला",
- "answerFour": "वापर फारसा केला नाही, त्यामुळे खर्च योग्य वाटला नाही",
- "answerFive": "सेवा समस्या किंवा तांत्रिक अडचणी"
- },
- "questionTwo": {
- "question": "भविष्यात @:appName Pro सदस्यता पुन्हा घेण्याची शक्यता किती आहे?",
- "answerOne": "खूप शक्यता आहे",
- "answerTwo": "काहीशी शक्यता आहे",
- "answerThree": "निश्चित नाही",
- "answerFour": "अल्प शक्यता",
- "answerFive": "एकदम कमी शक्यता"
- },
- "questionThree": {
- "question": "तुमच्या सदस्यत्वादरम्यान कोणते Pro वैशिष्ट्य सर्वात उपयुक्त वाटले?",
- "answerOne": "अनेक वापरकर्त्यांशी सहकार्य",
- "answerTwo": "लांब कालावधीची आवृत्ती इतिहास",
- "answerThree": "अमर्यादित AI प्रतिसाद",
- "answerFour": "स्थानिक AI मॉडेल्सचा प्रवेश"
- },
- "questionFour": {
- "question": "@:appName वापरण्याचा तुमचा एकूण अनुभव कसा होता?",
- "answerOne": "खूप छान",
- "answerTwo": "चांगला",
- "answerThree": "सरासरी",
- "answerFour": "सरासरीपेक्षा कमी",
- "answerFive": "असंतोषजनक"
- }
-},
- "common": {
- "uploadingFile": "फाईल अपलोड होत आहे. कृपया अॅप बंद करू नका",
- "uploadNotionSuccess": "तुमची Notion zip फाईल यशस्वीरित्या अपलोड झाली आहे. आयात पूर्ण झाल्यानंतर तुम्हाला पुष्टीकरण ईमेल मिळेल",
- "reset": "रीसेट करा"
-},
- "menu": {
- "appearance": "दृश्यरूप",
- "language": "भाषा",
- "user": "वापरकर्ता",
- "files": "फाईल्स",
- "notifications": "सूचना",
- "open": "सेटिंग्ज उघडा",
- "logout": "लॉगआउट",
- "logoutPrompt": "तुम्हाला नक्की लॉगआउट करायचे आहे का?",
- "selfEncryptionLogoutPrompt": "तुम्हाला खात्रीने लॉगआउट करायचे आहे का? कृपया खात्री करा की तुम्ही एनक्रिप्शन गुप्तकी कॉपी केली आहे",
- "syncSetting": "सिंक्रोनायझेशन सेटिंग",
- "cloudSettings": "क्लाऊड सेटिंग्ज",
- "enableSync": "सिंक्रोनायझेशन सक्षम करा",
- "enableSyncLog": "सिंक लॉगिंग सक्षम करा",
- "enableSyncLogWarning": "सिंक समस्यांचे निदान करण्यात मदतीसाठी धन्यवाद. हे तुमचे डॉक्युमेंट संपादन स्थानिक फाईलमध्ये लॉग करेल. कृपया सक्षम केल्यानंतर अॅप बंद करून पुन्हा उघडा",
- "enableEncrypt": "डेटा एन्क्रिप्ट करा",
- "cloudURL": "बेस URL",
- "webURL": "वेब URL",
- "invalidCloudURLScheme": "अवैध स्कीम",
- "cloudServerType": "क्लाऊड सर्व्हर",
- "cloudServerTypeTip": "कृपया लक्षात घ्या की क्लाऊड सर्व्हर बदलल्यास तुम्ही सध्या लॉगिन केलेले खाते लॉगआउट होऊ शकते",
- "cloudLocal": "स्थानिक",
- "cloudAppFlowy": "@:appName Cloud",
- "cloudAppFlowySelfHost": "@:appName क्लाऊड सेल्फ-होस्टेड",
- "appFlowyCloudUrlCanNotBeEmpty": "क्लाऊड URL रिकामा असू शकत नाही",
- "clickToCopy": "क्लिपबोर्डवर कॉपी करा",
- "selfHostStart": "जर तुमच्याकडे सर्व्हर नसेल, तर कृपया हे पाहा",
- "selfHostContent": "दस्तऐवज",
- "selfHostEnd": "तुमचा स्वतःचा सर्व्हर कसा होस्ट करावा यासाठी मार्गदर्शनासाठी",
- "pleaseInputValidURL": "कृपया वैध URL टाका",
- "changeUrl": "सेल्फ-होस्टेड URL {} मध्ये बदला",
- "cloudURLHint": "तुमच्या सर्व्हरचा बेस URL टाका",
- "webURLHint": "तुमच्या वेब सर्व्हरचा बेस URL टाका",
- "cloudWSURL": "वेबसॉकेट URL",
- "cloudWSURLHint": "तुमच्या सर्व्हरचा वेबसॉकेट पत्ता टाका",
- "restartApp": "अॅप रीस्टार्ट करा",
- "restartAppTip": "बदल प्रभावी होण्यासाठी अॅप रीस्टार्ट करा. कृपया लक्षात घ्या की यामुळे सध्याचे खाते लॉगआउट होऊ शकते.",
- "changeServerTip": "सर्व्हर बदलल्यानंतर, बदल लागू करण्यासाठी तुम्हाला 'रीस्टार्ट' बटणावर क्लिक करणे आवश्यक आहे",
- "enableEncryptPrompt": "तुमचा डेटा सुरक्षित करण्यासाठी एन्क्रिप्शन सक्रिय करा. ही गुप्तकी सुरक्षित ठेवा; एकदा सक्षम केल्यावर ती बंद करता येणार नाही. जर हरवली तर तुमचा डेटा पुन्हा मिळवता येणार नाही. कॉपी करण्यासाठी क्लिक करा",
- "inputEncryptPrompt": "कृपया खालीलसाठी तुमची एनक्रिप्शन गुप्तकी टाका:",
- "clickToCopySecret": "गुप्तकी कॉपी करण्यासाठी क्लिक करा",
- "configServerSetting": "तुमच्या सर्व्हर सेटिंग्ज कॉन्फिगर करा",
- "configServerGuide": "`Quick Start` निवडल्यानंतर, `Settings` → \"Cloud Settings\" मध्ये जा आणि तुमचा सेल्फ-होस्टेड सर्व्हर कॉन्फिगर करा.",
- "inputTextFieldHint": "तुमची गुप्तकी",
- "historicalUserList": "वापरकर्ता लॉगिन इतिहास",
- "historicalUserListTooltip": "ही यादी तुमची अनामिक खाती दर्शवते. तपशील पाहण्यासाठी खात्यावर क्लिक करा. अनामिक खाती 'सुरुवात करा' बटणावर क्लिक करून तयार केली जातात",
- "openHistoricalUser": "अनामिक खाते उघडण्यासाठी क्लिक करा",
- "customPathPrompt": "@:appName डेटा फोल्डर Google Drive सारख्या क्लाऊड-सिंक फोल्डरमध्ये साठवणे धोकादायक ठरू शकते. फोल्डरमधील डेटाबेस अनेक ठिकाणांवरून एकाच वेळी ऍक्सेस/संपादित केल्यास डेटा सिंक समस्या किंवा भ्रष्ट होण्याची शक्यता असते.",
- "importAppFlowyData": "बाह्य @:appName फोल्डरमधून डेटा आयात करा",
- "importingAppFlowyDataTip": "डेटा आयात सुरू आहे. कृपया अॅप बंद करू नका",
- "importAppFlowyDataDescription": "बाह्य @:appName फोल्डरमधून डेटा कॉपी करून सध्याच्या AppFlowy डेटा फोल्डरमध्ये आयात करा",
- "importSuccess": "@:appName डेटा फोल्डर यशस्वीरित्या आयात झाला",
- "importFailed": "@:appName डेटा फोल्डर आयात करण्यात अयशस्वी",
- "importGuide": "अधिक माहितीसाठी, कृपया संदर्भित दस्तऐवज पहा"
-},
- "notifications": {
- "enableNotifications": {
- "label": "सूचना सक्षम करा",
- "hint": "स्थानिक सूचना दिसू नयेत यासाठी बंद करा."
- },
- "showNotificationsIcon": {
- "label": "सूचना चिन्ह दाखवा",
- "hint": "साइडबारमध्ये सूचना चिन्ह लपवण्यासाठी बंद करा."
- },
- "archiveNotifications": {
- "allSuccess": "सर्व सूचना यशस्वीरित्या संग्रहित केल्या",
- "success": "सूचना यशस्वीरित्या संग्रहित केली"
- },
- "markAsReadNotifications": {
- "allSuccess": "सर्व वाचलेल्या म्हणून चिन्हांकित केल्या",
- "success": "वाचलेले म्हणून चिन्हांकित केले"
- },
- "action": {
- "markAsRead": "वाचलेले म्हणून चिन्हांकित करा",
- "multipleChoice": "अधिक निवडा",
- "archive": "संग्रहित करा"
- },
- "settings": {
- "settings": "सेटिंग्ज",
- "markAllAsRead": "सर्व वाचलेले म्हणून चिन्हांकित करा",
- "archiveAll": "सर्व संग्रहित करा"
- },
- "emptyInbox": {
- "title": "इनबॉक्स झिरो!",
- "description": "इथे सूचना मिळवण्यासाठी रिमाइंडर सेट करा."
- },
- "emptyUnread": {
- "title": "कोणतीही न वाचलेली सूचना नाही",
- "description": "तुम्ही सर्व वाचले आहे!"
- },
- "emptyArchived": {
- "title": "कोणतीही संग्रहित सूचना नाही",
- "description": "संग्रहित सूचना इथे दिसतील."
- },
- "tabs": {
- "inbox": "इनबॉक्स",
- "unread": "न वाचलेले",
- "archived": "संग्रहित"
- },
- "refreshSuccess": "सूचना यशस्वीरित्या रीफ्रेश केल्या",
- "titles": {
- "notifications": "सूचना",
- "reminder": "रिमाइंडर"
- }
-},
- "appearance": {
- "resetSetting": "रीसेट",
- "fontFamily": {
- "label": "फॉन्ट फॅमिली",
- "search": "शोध",
- "defaultFont": "सिस्टम"
- },
- "themeMode": {
- "label": "थीम मोड",
- "light": "लाइट मोड",
- "dark": "डार्क मोड",
- "system": "सिस्टमशी जुळवा"
- },
- "fontScaleFactor": "फॉन्ट स्केल घटक",
- "displaySize": "डिस्प्ले आकार",
- "documentSettings": {
- "cursorColor": "डॉक्युमेंट कर्सरचा रंग",
- "selectionColor": "डॉक्युमेंट निवडीचा रंग",
- "width": "डॉक्युमेंटची रुंदी",
- "changeWidth": "बदला",
- "pickColor": "रंग निवडा",
- "colorShade": "रंगाची छटा",
- "opacity": "अपारदर्शकता",
- "hexEmptyError": "Hex रंग रिकामा असू शकत नाही",
- "hexLengthError": "Hex व्हॅल्यू 6 अंकांची असावी",
- "hexInvalidError": "अवैध Hex व्हॅल्यू",
- "opacityEmptyError": "अपारदर्शकता रिकामी असू शकत नाही",
- "opacityRangeError": "अपारदर्शकता 1 ते 100 दरम्यान असावी",
- "app": "अॅप",
- "flowy": "Flowy",
- "apply": "लागू करा"
- },
- "layoutDirection": {
- "label": "लेआउट दिशा",
- "hint": "तुमच्या स्क्रीनवरील कंटेंटचा प्रवाह नियंत्रित करा — डावीकडून उजवीकडे किंवा उजवीकडून डावीकडे.",
- "ltr": "LTR",
- "rtl": "RTL"
- },
- "textDirection": {
- "label": "मूलभूत मजकूर दिशा",
- "hint": "मजकूर डावीकडून सुरू व्हावा की उजवीकडून याचे पूर्वनिर्धारित निर्धारण करा.",
- "ltr": "LTR",
- "rtl": "RTL",
- "auto": "स्वयं",
- "fallback": "लेआउट दिशेशी जुळवा"
- },
- "themeUpload": {
- "button": "अपलोड",
- "uploadTheme": "थीम अपलोड करा",
- "description": "खालील बटण वापरून तुमची स्वतःची @:appName थीम अपलोड करा.",
- "loading": "कृपया प्रतीक्षा करा. आम्ही तुमची थीम पडताळत आहोत आणि अपलोड करत आहोत...",
- "uploadSuccess": "तुमची थीम यशस्वीरित्या अपलोड झाली आहे",
- "deletionFailure": "थीम हटवण्यात अयशस्वी. कृपया ती मॅन्युअली हटवून पाहा.",
- "filePickerDialogTitle": ".flowy_plugin फाईल निवडा",
- "urlUploadFailure": "URL उघडण्यात अयशस्वी: {}"
- },
- "theme": "थीम",
- "builtInsLabel": "अंतर्गत थीम्स",
- "pluginsLabel": "प्लगइन्स",
- "dateFormat": {
- "label": "दिनांक फॉरमॅट",
- "local": "स्थानिक",
- "us": "US",
- "iso": "ISO",
- "friendly": "अनौपचारिक",
- "dmy": "D/M/Y"
- },
- "timeFormat": {
- "label": "वेळ फॉरमॅट",
- "twelveHour": "१२ तास",
- "twentyFourHour": "२४ तास"
- },
- "showNamingDialogWhenCreatingPage": "पृष्ठ तयार करताना नाव विचारणारा डायलॉग दाखवा",
- "enableRTLToolbarItems": "RTL टूलबार आयटम्स सक्षम करा",
- "members": {
- "title": "सदस्य सेटिंग्ज",
- "inviteMembers": "सदस्यांना आमंत्रण द्या",
- "inviteHint": "ईमेलद्वारे आमंत्रण द्या",
- "sendInvite": "आमंत्रण पाठवा",
- "copyInviteLink": "आमंत्रण दुवा कॉपी करा",
- "label": "सदस्य",
- "user": "वापरकर्ता",
- "role": "भूमिका",
- "removeFromWorkspace": "वर्कस्पेसमधून काढा",
- "removeFromWorkspaceSuccess": "वर्कस्पेसमधून यशस्वीरित्या काढले",
- "removeFromWorkspaceFailed": "वर्कस्पेसमधून काढण्यात अयशस्वी",
- "owner": "मालक",
- "guest": "अतिथी",
- "member": "सदस्य",
- "memberHintText": "सदस्य पृष्ठे वाचू व संपादित करू शकतो",
- "guestHintText": "अतिथी पृष्ठे वाचू शकतो, प्रतिक्रिया देऊ शकतो, टिप्पणी करू शकतो, आणि परवानगी असल्यास काही पृष्ठे संपादित करू शकतो.",
- "emailInvalidError": "अवैध ईमेल, कृपया तपासा व पुन्हा प्रयत्न करा",
- "emailSent": "ईमेल पाठवला गेला आहे, कृपया इनबॉक्स तपासा",
- "members": "सदस्य",
- "membersCount": {
- "zero": "{} सदस्य",
- "one": "{} सदस्य",
- "other": "{} सदस्य"
- },
- "inviteFailedDialogTitle": "आमंत्रण पाठवण्यात अयशस्वी",
- "inviteFailedMemberLimit": "सदस्य मर्यादा गाठली आहे, अधिक सदस्यांसाठी कृपया अपग्रेड करा.",
- "inviteFailedMemberLimitMobile": "तुमच्या वर्कस्पेसने सदस्य मर्यादा गाठली आहे.",
- "memberLimitExceeded": "सदस्य मर्यादा गाठली आहे, अधिक सदस्य आमंत्रित करण्यासाठी कृपया ",
- "memberLimitExceededUpgrade": "अपग्रेड करा",
- "memberLimitExceededPro": "सदस्य मर्यादा गाठली आहे, अधिक सदस्य आवश्यक असल्यास संपर्क करा",
- "memberLimitExceededProContact": "support@appflowy.io",
- "failedToAddMember": "सदस्य जोडण्यात अयशस्वी",
- "addMemberSuccess": "सदस्य यशस्वीरित्या जोडला गेला",
- "removeMember": "सदस्य काढा",
- "areYouSureToRemoveMember": "तुम्हाला हा सदस्य काढायचा आहे का?",
- "inviteMemberSuccess": "आमंत्रण यशस्वीरित्या पाठवले गेले",
- "failedToInviteMember": "सदस्य आमंत्रित करण्यात अयशस्वी",
- "workspaceMembersError": "अरे! काहीतरी चूक झाली आहे",
- "workspaceMembersErrorDescription": "आम्ही सध्या सदस्यांची यादी लोड करू शकत नाही. कृपया नंतर पुन्हा प्रयत्न करा"
- }
-},
- "files": {
- "copy": "कॉपी करा",
- "defaultLocation": "फाईल्स आणि डेटाचा संचय स्थान",
- "exportData": "तुमचा डेटा निर्यात करा",
- "doubleTapToCopy": "पथ कॉपी करण्यासाठी दोनदा टॅप करा",
- "restoreLocation": "@:appName चे मूळ स्थान पुनर्संचयित करा",
- "customizeLocation": "इतर फोल्डर उघडा",
- "restartApp": "बदल लागू करण्यासाठी कृपया अॅप रीस्टार्ट करा.",
- "exportDatabase": "डेटाबेस निर्यात करा",
- "selectFiles": "निर्यात करण्यासाठी फाईल्स निवडा",
- "selectAll": "सर्व निवडा",
- "deselectAll": "सर्व निवड रद्द करा",
- "createNewFolder": "नवीन फोल्डर तयार करा",
- "createNewFolderDesc": "तुमचा डेटा कुठे साठवायचा हे सांगा",
- "defineWhereYourDataIsStored": "तुमचा डेटा कुठे साठवला जातो हे ठरवा",
- "open": "उघडा",
- "openFolder": "आधीक फोल्डर उघडा",
- "openFolderDesc": "तुमच्या विद्यमान @:appName फोल्डरमध्ये वाचन व लेखन करा",
- "folderHintText": "फोल्डरचे नाव",
- "location": "नवीन फोल्डर तयार करत आहे",
- "locationDesc": "तुमच्या @:appName डेटासाठी नाव निवडा",
- "browser": "ब्राउझ करा",
- "create": "तयार करा",
- "set": "सेट करा",
- "folderPath": "फोल्डर साठवण्याचा मार्ग",
- "locationCannotBeEmpty": "मार्ग रिकामा असू शकत नाही",
- "pathCopiedSnackbar": "फाईल संचय मार्ग क्लिपबोर्डवर कॉपी केला!",
- "changeLocationTooltips": "डेटा डिरेक्टरी बदला",
- "change": "बदला",
- "openLocationTooltips": "इतर डेटा डिरेक्टरी उघडा",
- "openCurrentDataFolder": "सध्याची डेटा डिरेक्टरी उघडा",
- "recoverLocationTooltips": "@:appName च्या मूळ डेटा डिरेक्टरीवर रीसेट करा",
- "exportFileSuccess": "फाईल यशस्वीरित्या निर्यात झाली!",
- "exportFileFail": "फाईल निर्यात करण्यात अयशस्वी!",
- "export": "निर्यात करा",
- "clearCache": "कॅशे साफ करा",
- "clearCacheDesc": "प्रतिमा लोड होत नाहीत किंवा फॉन्ट अयोग्यरित्या दिसत असल्यास, कॅशे साफ करून पाहा. ही क्रिया तुमचा वापरकर्ता डेटा हटवणार नाही.",
- "areYouSureToClearCache": "तुम्हाला नक्की कॅशे साफ करायची आहे का?",
- "clearCacheSuccess": "कॅशे यशस्वीरित्या साफ झाली!"
-},
- "user": {
- "name": "नाव",
- "email": "ईमेल",
- "tooltipSelectIcon": "चिन्ह निवडा",
- "selectAnIcon": "चिन्ह निवडा",
- "pleaseInputYourOpenAIKey": "कृपया तुमची AI की टाका",
- "clickToLogout": "सध्याचा वापरकर्ता लॉगआउट करण्यासाठी क्लिक करा"
-},
- "mobile": {
- "personalInfo": "वैयक्तिक माहिती",
- "username": "वापरकर्तानाव",
- "usernameEmptyError": "वापरकर्तानाव रिकामे असू शकत नाही",
- "about": "विषयी",
- "pushNotifications": "पुश सूचना",
- "support": "सपोर्ट",
- "joinDiscord": "Discord मध्ये सहभागी व्हा",
- "privacyPolicy": "गोपनीयता धोरण",
- "userAgreement": "वापरकर्ता करार",
- "termsAndConditions": "अटी व शर्ती",
- "userprofileError": "वापरकर्ता प्रोफाइल लोड करण्यात अयशस्वी",
- "userprofileErrorDescription": "कृपया लॉगआउट करून पुन्हा लॉगिन करा आणि त्रुटी अजूनही येते का ते पहा.",
- "selectLayout": "लेआउट निवडा",
- "selectStartingDay": "सप्ताहाचा प्रारंभ दिवस निवडा",
- "version": "आवृत्ती"
-},
- "grid": {
- "deleteView": "तुम्हाला हे दृश्य हटवायचे आहे का?",
- "createView": "नवीन",
- "title": {
- "placeholder": "नाव नाही"
- },
- "settings": {
- "filter": "फिल्टर",
- "sort": "क्रमवारी",
- "sortBy": "यावरून क्रमवारी लावा",
- "properties": "गुणधर्म",
- "reorderPropertiesTooltip": "गुणधर्मांचे स्थान बदला",
- "group": "समूह",
- "addFilter": "फिल्टर जोडा",
- "deleteFilter": "फिल्टर हटवा",
- "filterBy": "यावरून फिल्टर करा",
- "typeAValue": "मूल्य लिहा...",
- "layout": "लेआउट",
- "compactMode": "कॉम्पॅक्ट मोड",
- "databaseLayout": "लेआउट",
- "viewList": {
- "zero": "० दृश्ये",
- "one": "{count} दृश्य",
- "other": "{count} दृश्ये"
- },
- "editView": "दृश्य संपादित करा",
- "boardSettings": "बोर्ड सेटिंग",
- "calendarSettings": "कॅलेंडर सेटिंग",
- "createView": "नवीन दृश्य",
- "duplicateView": "दृश्याची प्रत बनवा",
- "deleteView": "दृश्य हटवा",
- "numberOfVisibleFields": "{} दर्शविले"
- },
- "filter": {
- "empty": "कोणतेही सक्रिय फिल्टर नाहीत",
- "addFilter": "फिल्टर जोडा",
- "cannotFindCreatableField": "फिल्टर करण्यासाठी योग्य फील्ड सापडले नाही",
- "conditon": "अट",
- "where": "जिथे"
- },
- "textFilter": {
- "contains": "अंतर्भूत आहे",
- "doesNotContain": "अंतर्भूत नाही",
- "endsWith": "याने समाप्त होते",
- "startWith": "याने सुरू होते",
- "is": "आहे",
- "isNot": "नाही",
- "isEmpty": "रिकामे आहे",
- "isNotEmpty": "रिकामे नाही",
- "choicechipPrefix": {
- "isNot": "नाही",
- "startWith": "याने सुरू होते",
- "endWith": "याने समाप्त होते",
- "isEmpty": "रिकामे आहे",
- "isNotEmpty": "रिकामे नाही"
- }
- },
- "checkboxFilter": {
- "isChecked": "निवडलेले आहे",
- "isUnchecked": "निवडलेले नाही",
- "choicechipPrefix": {
- "is": "आहे"
- }
- },
- "checklistFilter": {
- "isComplete": "पूर्ण झाले आहे",
- "isIncomplted": "अपूर्ण आहे"
- },
- "selectOptionFilter": {
- "is": "आहे",
- "isNot": "नाही",
- "contains": "अंतर्भूत आहे",
- "doesNotContain": "अंतर्भूत नाही",
- "isEmpty": "रिकामे आहे",
- "isNotEmpty": "रिकामे नाही"
-},
-"dateFilter": {
- "is": "या दिवशी आहे",
- "before": "पूर्वी आहे",
- "after": "नंतर आहे",
- "onOrBefore": "या दिवशी किंवा त्याआधी आहे",
- "onOrAfter": "या दिवशी किंवा त्यानंतर आहे",
- "between": "दरम्यान आहे",
- "empty": "रिकामे आहे",
- "notEmpty": "रिकामे नाही",
- "startDate": "सुरुवातीची तारीख",
- "endDate": "शेवटची तारीख",
- "choicechipPrefix": {
- "before": "पूर्वी",
- "after": "नंतर",
- "between": "दरम्यान",
- "onOrBefore": "या दिवशी किंवा त्याआधी",
- "onOrAfter": "या दिवशी किंवा त्यानंतर",
- "isEmpty": "रिकामे आहे",
- "isNotEmpty": "रिकामे नाही"
- }
-},
-"numberFilter": {
- "equal": "बरोबर आहे",
- "notEqual": "बरोबर नाही",
- "lessThan": "पेक्षा कमी आहे",
- "greaterThan": "पेक्षा जास्त आहे",
- "lessThanOrEqualTo": "किंवा कमी आहे",
- "greaterThanOrEqualTo": "किंवा जास्त आहे",
- "isEmpty": "रिकामे आहे",
- "isNotEmpty": "रिकामे नाही"
-},
-"field": {
- "label": "गुणधर्म",
- "hide": "गुणधर्म लपवा",
- "show": "गुणधर्म दर्शवा",
- "insertLeft": "डावीकडे जोडा",
- "insertRight": "उजवीकडे जोडा",
- "duplicate": "प्रत बनवा",
- "delete": "हटवा",
- "wrapCellContent": "पाठ लपेटा",
- "clear": "सेल्स रिकामे करा",
- "switchPrimaryFieldTooltip": "प्राथमिक फील्डचा प्रकार बदलू शकत नाही",
- "textFieldName": "मजकूर",
- "checkboxFieldName": "चेकबॉक्स",
- "dateFieldName": "तारीख",
- "updatedAtFieldName": "शेवटचे अपडेट",
- "createdAtFieldName": "तयार झाले",
- "numberFieldName": "संख्या",
- "singleSelectFieldName": "सिंगल सिलेक्ट",
- "multiSelectFieldName": "मल्टीसिलेक्ट",
- "urlFieldName": "URL",
- "checklistFieldName": "चेकलिस्ट",
- "relationFieldName": "संबंध",
- "summaryFieldName": "AI सारांश",
- "timeFieldName": "वेळ",
- "mediaFieldName": "फाईल्स आणि मीडिया",
- "translateFieldName": "AI भाषांतर",
- "translateTo": "मध्ये भाषांतर करा",
- "numberFormat": "संख्या स्वरूप",
- "dateFormat": "तारीख स्वरूप",
- "includeTime": "वेळ जोडा",
- "isRange": "शेवटची तारीख",
- "dateFormatFriendly": "महिना दिवस, वर्ष",
- "dateFormatISO": "वर्ष-महिना-दिनांक",
- "dateFormatLocal": "महिना/दिवस/वर्ष",
- "dateFormatUS": "वर्ष/महिना/दिवस",
- "dateFormatDayMonthYear": "दिवस/महिना/वर्ष",
- "timeFormat": "वेळ स्वरूप",
- "invalidTimeFormat": "अवैध स्वरूप",
- "timeFormatTwelveHour": "१२ तास",
- "timeFormatTwentyFourHour": "२४ तास",
- "clearDate": "तारीख हटवा",
- "dateTime": "तारीख व वेळ",
- "startDateTime": "सुरुवातीची तारीख व वेळ",
- "endDateTime": "शेवटची तारीख व वेळ",
- "failedToLoadDate": "तारीख मूल्य लोड करण्यात अयशस्वी",
- "selectTime": "वेळ निवडा",
- "selectDate": "तारीख निवडा",
- "visibility": "दृश्यता",
- "propertyType": "गुणधर्माचा प्रकार",
- "addSelectOption": "पर्याय जोडा",
- "typeANewOption": "नवीन पर्याय लिहा",
- "optionTitle": "पर्याय",
- "addOption": "पर्याय जोडा",
- "editProperty": "गुणधर्म संपादित करा",
- "newProperty": "नवीन गुणधर्म",
- "openRowDocument": "पृष्ठ म्हणून उघडा",
- "deleteFieldPromptMessage": "तुम्हाला खात्री आहे का? हा गुणधर्म आणि त्याचा डेटा हटवला जाईल",
- "clearFieldPromptMessage": "तुम्हाला खात्री आहे का? या कॉलममधील सर्व सेल्स रिकामे होतील",
- "newColumn": "नवीन कॉलम",
- "format": "स्वरूप",
- "reminderOnDateTooltip": "या सेलमध्ये अनुस्मारक शेड्यूल केले आहे",
- "optionAlreadyExist": "पर्याय आधीच अस्तित्वात आहे"
-},
- "rowPage": {
- "newField": "नवीन फील्ड जोडा",
- "fieldDragElementTooltip": "मेनू उघडण्यासाठी क्लिक करा",
- "showHiddenFields": {
- "one": "{count} लपलेले फील्ड दाखवा",
- "many": "{count} लपलेली फील्ड दाखवा",
- "other": "{count} लपलेली फील्ड दाखवा"
- },
- "hideHiddenFields": {
- "one": "{count} लपलेले फील्ड लपवा",
- "many": "{count} लपलेली फील्ड लपवा",
- "other": "{count} लपलेली फील्ड लपवा"
- },
- "openAsFullPage": "पूर्ण पृष्ठ म्हणून उघडा",
- "moreRowActions": "अधिक पंक्ती क्रिया"
-},
-"sort": {
- "ascending": "चढत्या क्रमाने",
- "descending": "उतरत्या क्रमाने",
- "by": "द्वारे",
- "empty": "सक्रिय सॉर्ट्स नाहीत",
- "cannotFindCreatableField": "सॉर्टसाठी योग्य फील्ड सापडले नाही",
- "deleteAllSorts": "सर्व सॉर्ट्स हटवा",
- "addSort": "सॉर्ट जोडा",
- "sortsActive": "सॉर्टिंग करत असताना {intention} करू शकत नाही",
- "removeSorting": "या दृश्यातील सर्व सॉर्ट्स हटवायचे आहेत का?",
- "fieldInUse": "या फील्डवर आधीच सॉर्टिंग चालू आहे"
-},
-"row": {
- "label": "पंक्ती",
- "duplicate": "प्रत बनवा",
- "delete": "हटवा",
- "titlePlaceholder": "शीर्षक नाही",
- "textPlaceholder": "रिक्त",
- "copyProperty": "गुणधर्म क्लिपबोर्डवर कॉपी केला",
- "count": "संख्या",
- "newRow": "नवीन पंक्ती",
- "loadMore": "अधिक लोड करा",
- "action": "क्रिया",
- "add": "खाली जोडा वर क्लिक करा",
- "drag": "हलवण्यासाठी ड्रॅग करा",
- "deleteRowPrompt": "तुम्हाला खात्री आहे का की ही पंक्ती हटवायची आहे? ही क्रिया पूर्ववत करता येणार नाही.",
- "deleteCardPrompt": "तुम्हाला खात्री आहे का की हे कार्ड हटवायचे आहे? ही क्रिया पूर्ववत करता येणार नाही.",
- "dragAndClick": "हलवण्यासाठी ड्रॅग करा, मेनू उघडण्यासाठी क्लिक करा",
- "insertRecordAbove": "वर रेकॉर्ड जोडा",
- "insertRecordBelow": "खाली रेकॉर्ड जोडा",
- "noContent": "माहिती नाही",
- "reorderRowDescription": "पंक्तीचे पुन्हा क्रमांकन",
- "createRowAboveDescription": "वर पंक्ती तयार करा",
- "createRowBelowDescription": "खाली पंक्ती जोडा"
-},
-"selectOption": {
- "create": "तयार करा",
- "purpleColor": "जांभळा",
- "pinkColor": "गुलाबी",
- "lightPinkColor": "फिकट गुलाबी",
- "orangeColor": "नारंगी",
- "yellowColor": "पिवळा",
- "limeColor": "लिंबू",
- "greenColor": "हिरवा",
- "aquaColor": "आक्वा",
- "blueColor": "निळा",
- "deleteTag": "टॅग हटवा",
- "colorPanelTitle": "रंग",
- "panelTitle": "पर्याय निवडा किंवा नवीन तयार करा",
- "searchOption": "पर्याय शोधा",
- "searchOrCreateOption": "पर्याय शोधा किंवा तयार करा",
- "createNew": "नवीन तयार करा",
- "orSelectOne": "किंवा पर्याय निवडा",
- "typeANewOption": "नवीन पर्याय टाइप करा",
- "tagName": "टॅग नाव"
-},
-"checklist": {
- "taskHint": "कार्याचे वर्णन",
- "addNew": "नवीन कार्य जोडा",
- "submitNewTask": "तयार करा",
- "hideComplete": "पूर्ण कार्ये लपवा",
- "showComplete": "सर्व कार्ये दाखवा"
-},
-"url": {
- "launch": "बाह्य ब्राउझरमध्ये लिंक उघडा",
- "copy": "लिंक क्लिपबोर्डवर कॉपी करा",
- "textFieldHint": "URL टाका",
- "copiedNotification": "क्लिपबोर्डवर कॉपी केले!"
-},
-"relation": {
- "relatedDatabasePlaceLabel": "संबंधित डेटाबेस",
- "relatedDatabasePlaceholder": "काही नाही",
- "inRelatedDatabase": "या मध्ये",
- "rowSearchTextFieldPlaceholder": "शोध",
- "noDatabaseSelected": "कोणताही डेटाबेस निवडलेला नाही, कृपया खालील यादीतून एक निवडा:",
- "emptySearchResult": "कोणतीही नोंद सापडली नाही",
- "linkedRowListLabel": "{count} लिंक केलेल्या पंक्ती",
- "unlinkedRowListLabel": "आणखी एक पंक्ती लिंक करा"
-},
-"menuName": "ग्रिड",
-"referencedGridPrefix": "दृश्य",
-"calculate": "गणना करा",
-"calculationTypeLabel": {
- "none": "काही नाही",
- "average": "सरासरी",
- "max": "कमाल",
- "median": "मध्यम",
- "min": "किमान",
- "sum": "बेरीज",
- "count": "मोजणी",
- "countEmpty": "रिकाम्यांची मोजणी",
- "countEmptyShort": "रिक्त",
- "countNonEmpty": "रिक्त नसलेल्यांची मोजणी",
- "countNonEmptyShort": "भरलेले"
-},
-"media": {
- "rename": "पुन्हा नाव द्या",
- "download": "डाउनलोड करा",
- "expand": "मोठे करा",
- "delete": "हटवा",
- "moreFilesHint": "+{}",
- "addFileOrImage": "फाईल किंवा लिंक जोडा",
- "attachmentsHint": "{}",
- "addFileMobile": "फाईल जोडा",
- "extraCount": "+{}",
- "deleteFileDescription": "तुम्हाला खात्री आहे का की ही फाईल हटवायची आहे? ही क्रिया पूर्ववत करता येणार नाही.",
- "showFileNames": "फाईलचे नाव दाखवा",
- "downloadSuccess": "फाईल डाउनलोड झाली",
- "downloadFailedToken": "फाईल डाउनलोड अयशस्वी, वापरकर्ता टोकन अनुपलब्ध",
- "setAsCover": "कव्हर म्हणून सेट करा",
- "openInBrowser": "ब्राउझरमध्ये उघडा",
- "embedLink": "फाईल लिंक एम्बेड करा"
- }
-},
- "document": {
- "menuName": "दस्तऐवज",
- "date": {
- "timeHintTextInTwelveHour": "01:00 PM",
- "timeHintTextInTwentyFourHour": "13:00"
- },
- "creating": "तयार करत आहे...",
- "slashMenu": {
- "board": {
- "selectABoardToLinkTo": "लिंक करण्यासाठी बोर्ड निवडा",
- "createANewBoard": "नवीन बोर्ड तयार करा"
- },
- "grid": {
- "selectAGridToLinkTo": "लिंक करण्यासाठी ग्रिड निवडा",
- "createANewGrid": "नवीन ग्रिड तयार करा"
- },
- "calendar": {
- "selectACalendarToLinkTo": "लिंक करण्यासाठी दिनदर्शिका निवडा",
- "createANewCalendar": "नवीन दिनदर्शिका तयार करा"
- },
- "document": {
- "selectADocumentToLinkTo": "लिंक करण्यासाठी दस्तऐवज निवडा"
- },
- "name": {
- "textStyle": "मजकुराची शैली",
- "list": "यादी",
- "toggle": "टॉगल",
- "fileAndMedia": "फाईल व मीडिया",
- "simpleTable": "सोपे टेबल",
- "visuals": "दृश्य घटक",
- "document": "दस्तऐवज",
- "advanced": "प्रगत",
- "text": "मजकूर",
- "heading1": "शीर्षक 1",
- "heading2": "शीर्षक 2",
- "heading3": "शीर्षक 3",
- "image": "प्रतिमा",
- "bulletedList": "बुलेट यादी",
- "numberedList": "क्रमांकित यादी",
- "todoList": "करण्याची यादी",
- "doc": "दस्तऐवज",
- "linkedDoc": "पृष्ठाशी लिंक करा",
- "grid": "ग्रिड",
- "linkedGrid": "लिंक केलेला ग्रिड",
- "kanban": "कानबन",
- "linkedKanban": "लिंक केलेला कानबन",
- "calendar": "दिनदर्शिका",
- "linkedCalendar": "लिंक केलेली दिनदर्शिका",
- "quote": "उद्धरण",
- "divider": "विभाजक",
- "table": "टेबल",
- "callout": "महत्त्वाचा मजकूर",
- "outline": "रूपरेषा",
- "mathEquation": "गणिती समीकरण",
- "code": "कोड",
- "toggleList": "टॉगल यादी",
- "toggleHeading1": "टॉगल शीर्षक 1",
- "toggleHeading2": "टॉगल शीर्षक 2",
- "toggleHeading3": "टॉगल शीर्षक 3",
- "emoji": "इमोजी",
- "aiWriter": "AI ला काहीही विचारा",
- "dateOrReminder": "दिनांक किंवा स्मरणपत्र",
- "photoGallery": "फोटो गॅलरी",
- "file": "फाईल",
- "twoColumns": "२ स्तंभ",
- "threeColumns": "३ स्तंभ",
- "fourColumns": "४ स्तंभ"
- },
- "subPage": {
- "name": "दस्तऐवज",
- "keyword1": "उपपृष्ठ",
- "keyword2": "पृष्ठ",
- "keyword3": "चाइल्ड पृष्ठ",
- "keyword4": "पृष्ठ जोडा",
- "keyword5": "एम्बेड पृष्ठ",
- "keyword6": "नवीन पृष्ठ",
- "keyword7": "पृष्ठ तयार करा",
- "keyword8": "दस्तऐवज"
- }
- },
- "selectionMenu": {
- "outline": "रूपरेषा",
- "codeBlock": "कोड ब्लॉक"
- },
- "plugins": {
- "referencedBoard": "संदर्भित बोर्ड",
- "referencedGrid": "संदर्भित ग्रिड",
- "referencedCalendar": "संदर्भित दिनदर्शिका",
- "referencedDocument": "संदर्भित दस्तऐवज",
- "aiWriter": {
- "userQuestion": "AI ला काहीही विचारा",
- "continueWriting": "लेखन सुरू ठेवा",
- "fixSpelling": "स्पेलिंग व व्याकरण सुधारणा",
- "improveWriting": "लेखन सुधारित करा",
- "summarize": "सारांश द्या",
- "explain": "स्पष्टीकरण द्या",
- "makeShorter": "लहान करा",
- "makeLonger": "मोठे करा"
- },
- "autoGeneratorMenuItemName": "AI लेखक",
-"autoGeneratorTitleName": "AI: काहीही लिहिण्यासाठी AI ला विचारा...",
-"autoGeneratorLearnMore": "अधिक जाणून घ्या",
-"autoGeneratorGenerate": "उत्पन्न करा",
-"autoGeneratorHintText": "AI ला विचारा...",
-"autoGeneratorCantGetOpenAIKey": "AI की मिळवू शकलो नाही",
-"autoGeneratorRewrite": "पुन्हा लिहा",
-"smartEdit": "AI ला विचारा",
-"aI": "AI",
-"smartEditFixSpelling": "स्पेलिंग आणि व्याकरण सुधारा",
-"warning": "⚠️ AI उत्तरं चुकीची किंवा दिशाभूल करणारी असू शकतात.",
-"smartEditSummarize": "सारांश द्या",
-"smartEditImproveWriting": "लेखन सुधारित करा",
-"smartEditMakeLonger": "लांब करा",
-"smartEditCouldNotFetchResult": "AI कडून उत्तर मिळवता आले नाही",
-"smartEditCouldNotFetchKey": "AI की मिळवता आली नाही",
-"smartEditDisabled": "सेटिंग्जमध्ये AI कनेक्ट करा",
-"appflowyAIEditDisabled": "AI वैशिष्ट्ये सक्षम करण्यासाठी साइन इन करा",
-"discardResponse": "AI उत्तर फेकून द्यायचं आहे का?",
-"createInlineMathEquation": "समीकरण तयार करा",
-"fonts": "फॉन्ट्स",
-"insertDate": "तारीख जोडा",
-"emoji": "इमोजी",
-"toggleList": "टॉगल यादी",
-"emptyToggleHeading": "रिकामे टॉगल h{}. मजकूर जोडण्यासाठी क्लिक करा.",
-"emptyToggleList": "रिकामी टॉगल यादी. मजकूर जोडण्यासाठी क्लिक करा.",
-"emptyToggleHeadingWeb": "रिकामे टॉगल h{level}. मजकूर जोडण्यासाठी क्लिक करा",
-"quoteList": "उद्धरण यादी",
-"numberedList": "क्रमांकित यादी",
-"bulletedList": "बुलेट यादी",
-"todoList": "करण्याची यादी",
-"callout": "ठळक मजकूर",
-"simpleTable": {
- "moreActions": {
- "color": "रंग",
- "align": "पंक्तिबद्ध करा",
- "delete": "हटा",
- "duplicate": "डुप्लिकेट करा",
- "insertLeft": "डावीकडे घाला",
- "insertRight": "उजवीकडे घाला",
- "insertAbove": "वर घाला",
- "insertBelow": "खाली घाला",
- "headerColumn": "हेडर स्तंभ",
- "headerRow": "हेडर ओळ",
- "clearContents": "सामग्री साफ करा",
- "setToPageWidth": "पृष्ठाच्या रुंदीप्रमाणे सेट करा",
- "distributeColumnsWidth": "स्तंभ समान करा",
- "duplicateRow": "ओळ डुप्लिकेट करा",
- "duplicateColumn": "स्तंभ डुप्लिकेट करा",
- "textColor": "मजकूराचा रंग",
- "cellBackgroundColor": "सेलचा पार्श्वभूमी रंग",
- "duplicateTable": "टेबल डुप्लिकेट करा"
- },
- "clickToAddNewRow": "नवीन ओळ जोडण्यासाठी क्लिक करा",
- "clickToAddNewColumn": "नवीन स्तंभ जोडण्यासाठी क्लिक करा",
- "clickToAddNewRowAndColumn": "नवीन ओळ आणि स्तंभ जोडण्यासाठी क्लिक करा",
- "headerName": {
- "table": "टेबल",
- "alignText": "मजकूर पंक्तिबद्ध करा"
- }
-},
-"cover": {
- "changeCover": "कव्हर बदला",
- "colors": "रंग",
- "images": "प्रतिमा",
- "clearAll": "सर्व साफ करा",
- "abstract": "ऍबस्ट्रॅक्ट",
- "addCover": "कव्हर जोडा",
- "addLocalImage": "स्थानिक प्रतिमा जोडा",
- "invalidImageUrl": "अवैध प्रतिमा URL",
- "failedToAddImageToGallery": "प्रतिमा गॅलरीत जोडता आली नाही",
- "enterImageUrl": "प्रतिमा URL लिहा",
- "add": "जोडा",
- "back": "मागे",
- "saveToGallery": "गॅलरीत जतन करा",
- "removeIcon": "आयकॉन काढा",
- "removeCover": "कव्हर काढा",
- "pasteImageUrl": "प्रतिमा URL पेस्ट करा",
- "or": "किंवा",
- "pickFromFiles": "फाईल्समधून निवडा",
- "couldNotFetchImage": "प्रतिमा मिळवता आली नाही",
- "imageSavingFailed": "प्रतिमा जतन करणे अयशस्वी",
- "addIcon": "आयकॉन जोडा",
- "changeIcon": "आयकॉन बदला",
- "coverRemoveAlert": "हे हटवल्यानंतर कव्हरमधून काढले जाईल.",
- "alertDialogConfirmation": "तुम्हाला खात्री आहे का? तुम्हाला पुढे जायचे आहे?"
-},
-"mathEquation": {
- "name": "गणिती समीकरण",
- "addMathEquation": "TeX समीकरण जोडा",
- "editMathEquation": "गणिती समीकरण संपादित करा"
-},
-"optionAction": {
- "click": "क्लिक",
- "toOpenMenu": "मेनू उघडण्यासाठी",
- "drag": "ओढा",
- "toMove": "हलवण्यासाठी",
- "delete": "हटा",
- "duplicate": "डुप्लिकेट करा",
- "turnInto": "मध्ये बदला",
- "moveUp": "वर हलवा",
- "moveDown": "खाली हलवा",
- "color": "रंग",
- "align": "पंक्तिबद्ध करा",
- "left": "डावीकडे",
- "center": "मध्यभागी",
- "right": "उजवीकडे",
- "defaultColor": "डिफॉल्ट",
- "depth": "खोली",
- "copyLinkToBlock": "ब्लॉकसाठी लिंक कॉपी करा"
-},
- "image": {
- "addAnImage": "प्रतिमा जोडा",
- "copiedToPasteBoard": "प्रतिमेची लिंक क्लिपबोर्डवर कॉपी केली गेली आहे",
- "addAnImageDesktop": "प्रतिमा जोडा",
- "addAnImageMobile": "एक किंवा अधिक प्रतिमा जोडण्यासाठी क्लिक करा",
- "dropImageToInsert": "प्रतिमा ड्रॉप करून जोडा",
- "imageUploadFailed": "प्रतिमा अपलोड करण्यात अयशस्वी",
- "imageDownloadFailed": "प्रतिमा डाउनलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा",
- "imageDownloadFailedToken": "युजर टोकन नसल्यामुळे प्रतिमा डाउनलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा",
- "errorCode": "त्रुटी कोड"
-},
-"photoGallery": {
- "name": "फोटो गॅलरी",
- "imageKeyword": "प्रतिमा",
- "imageGalleryKeyword": "प्रतिमा गॅलरी",
- "photoKeyword": "फोटो",
- "photoBrowserKeyword": "फोटो ब्राउझर",
- "galleryKeyword": "गॅलरी",
- "addImageTooltip": "प्रतिमा जोडा",
- "changeLayoutTooltip": "लेआउट बदला",
- "browserLayout": "ब्राउझर",
- "gridLayout": "ग्रिड",
- "deleteBlockTooltip": "संपूर्ण गॅलरी हटवा"
-},
-"math": {
- "copiedToPasteBoard": "समीकरण क्लिपबोर्डवर कॉपी केले गेले आहे"
-},
-"urlPreview": {
- "copiedToPasteBoard": "लिंक क्लिपबोर्डवर कॉपी केली गेली आहे",
- "convertToLink": "एंबेड लिंकमध्ये रूपांतर करा"
-},
-"outline": {
- "addHeadingToCreateOutline": "सामग्री यादी तयार करण्यासाठी शीर्षके जोडा.",
- "noMatchHeadings": "जुळणारी शीर्षके आढळली नाहीत."
-},
-"table": {
- "addAfter": "नंतर जोडा",
- "addBefore": "आधी जोडा",
- "delete": "हटा",
- "clear": "सामग्री साफ करा",
- "duplicate": "डुप्लिकेट करा",
- "bgColor": "पार्श्वभूमीचा रंग"
-},
-"contextMenu": {
- "copy": "कॉपी करा",
- "cut": "कापा",
- "paste": "पेस्ट करा",
- "pasteAsPlainText": "साध्या मजकूराच्या स्वरूपात पेस्ट करा"
-},
-"action": "कृती",
-"database": {
- "selectDataSource": "डेटा स्रोत निवडा",
- "noDataSource": "डेटा स्रोत नाही",
- "selectADataSource": "डेटा स्रोत निवडा",
- "toContinue": "पुढे जाण्यासाठी",
- "newDatabase": "नवीन डेटाबेस",
- "linkToDatabase": "डेटाबेसशी लिंक करा"
-},
-"date": "तारीख",
-"video": {
- "label": "व्हिडिओ",
- "emptyLabel": "व्हिडिओ जोडा",
- "placeholder": "व्हिडिओ लिंक पेस्ट करा",
- "copiedToPasteBoard": "व्हिडिओ लिंक क्लिपबोर्डवर कॉपी केली गेली आहे",
- "insertVideo": "व्हिडिओ जोडा",
- "invalidVideoUrl": "ही URL सध्या समर्थित नाही.",
- "invalidVideoUrlYouTube": "YouTube सध्या समर्थित नाही.",
- "supportedFormats": "समर्थित स्वरूप: MP4, WebM, MOV, AVI, FLV, MPEG/M4V, H.264"
-},
-"file": {
- "name": "फाईल",
- "uploadTab": "अपलोड",
- "uploadMobile": "फाईल निवडा",
- "uploadMobileGallery": "फोटो गॅलरीमधून",
- "networkTab": "लिंक एम्बेड करा",
- "placeholderText": "फाईल अपलोड किंवा एम्बेड करा",
- "placeholderDragging": "फाईल ड्रॉप करून अपलोड करा",
- "dropFileToUpload": "फाईल ड्रॉप करून अपलोड करा",
- "fileUploadHint": "फाईल ड्रॅग आणि ड्रॉप करा किंवा क्लिक करा ",
- "fileUploadHintSuffix": "ब्राउझ करा",
- "networkHint": "फाईल लिंक पेस्ट करा",
- "networkUrlInvalid": "अवैध URL. कृपया तपासा आणि पुन्हा प्रयत्न करा.",
- "networkAction": "एम्बेड",
- "fileTooBigError": "फाईल खूप मोठी आहे, कृपया 10MB पेक्षा कमी फाईल अपलोड करा",
- "renameFile": {
- "title": "फाईलचे नाव बदला",
- "description": "या फाईलसाठी नवीन नाव लिहा",
- "nameEmptyError": "फाईलचे नाव रिकामे असू शकत नाही."
- },
- "uploadedAt": "{} रोजी अपलोड केले",
- "linkedAt": "{} रोजी लिंक जोडली",
- "failedToOpenMsg": "उघडण्यात अयशस्वी, फाईल सापडली नाही"
-},
-"subPage": {
- "handlingPasteHint": " - (पेस्ट प्रक्रिया करत आहे)",
- "errors": {
- "failedDeletePage": "पृष्ठ हटवण्यात अयशस्वी",
- "failedCreatePage": "पृष्ठ तयार करण्यात अयशस्वी",
- "failedMovePage": "हे पृष्ठ हलवण्यात अयशस्वी",
- "failedDuplicatePage": "पृष्ठ डुप्लिकेट करण्यात अयशस्वी",
- "failedDuplicateFindView": "पृष्ठ डुप्लिकेट करण्यात अयशस्वी - मूळ दृश्य सापडले नाही"
- }
-},
- "cannotMoveToItsChildren": "मुलांमध्ये हलवू शकत नाही"
-},
-"outlineBlock": {
- "placeholder": "सामग्री सूची"
-},
-"textBlock": {
- "placeholder": "कमांडसाठी '/' टाइप करा"
-},
-"title": {
- "placeholder": "शीर्षक नाही"
-},
-"imageBlock": {
- "placeholder": "प्रतिमा जोडण्यासाठी क्लिक करा",
- "upload": {
- "label": "अपलोड",
- "placeholder": "प्रतिमा अपलोड करण्यासाठी क्लिक करा"
- },
- "url": {
- "label": "प्रतिमेची URL",
- "placeholder": "प्रतिमेची URL टाका"
- },
- "ai": {
- "label": "AI द्वारे प्रतिमा तयार करा",
- "placeholder": "AI द्वारे प्रतिमा तयार करण्यासाठी कृपया संकेत द्या"
- },
- "stability_ai": {
- "label": "Stability AI द्वारे प्रतिमा तयार करा",
- "placeholder": "Stability AI द्वारे प्रतिमा तयार करण्यासाठी कृपया संकेत द्या"
- },
- "support": "प्रतिमेचा कमाल आकार 5MB आहे. समर्थित स्वरूप: JPEG, PNG, GIF, SVG",
- "error": {
- "invalidImage": "अवैध प्रतिमा",
- "invalidImageSize": "प्रतिमेचा आकार 5MB पेक्षा कमी असावा",
- "invalidImageFormat": "प्रतिमेचे स्वरूप समर्थित नाही. समर्थित स्वरूप: JPEG, PNG, JPG, GIF, SVG, WEBP",
- "invalidImageUrl": "अवैध प्रतिमेची URL",
- "noImage": "अशी फाईल किंवा निर्देशिका नाही",
- "multipleImagesFailed": "एक किंवा अधिक प्रतिमा अपलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा"
- },
- "embedLink": {
- "label": "लिंक एम्बेड करा",
- "placeholder": "प्रतिमेची लिंक पेस्ट करा किंवा टाका"
- },
- "unsplash": {
- "label": "Unsplash"
- },
- "searchForAnImage": "प्रतिमा शोधा",
- "pleaseInputYourOpenAIKey": "कृपया सेटिंग्ज पृष्ठात आपला AI की प्रविष्ट करा",
- "saveImageToGallery": "प्रतिमा जतन करा",
- "failedToAddImageToGallery": "प्रतिमा जतन करण्यात अयशस्वी",
- "successToAddImageToGallery": "प्रतिमा 'Photos' मध्ये जतन केली",
- "unableToLoadImage": "प्रतिमा लोड करण्यात अयशस्वी",
- "maximumImageSize": "कमाल प्रतिमा अपलोड आकार 10MB आहे",
- "uploadImageErrorImageSizeTooBig": "प्रतिमेचा आकार 10MB पेक्षा कमी असावा",
- "imageIsUploading": "प्रतिमा अपलोड होत आहे",
- "openFullScreen": "पूर्ण स्क्रीनमध्ये उघडा",
- "interactiveViewer": {
- "toolbar": {
- "previousImageTooltip": "मागील प्रतिमा",
- "nextImageTooltip": "पुढील प्रतिमा",
- "zoomOutTooltip": "लहान करा",
- "zoomInTooltip": "मोठी करा",
- "changeZoomLevelTooltip": "झूम पातळी बदला",
- "openLocalImage": "प्रतिमा उघडा",
- "downloadImage": "प्रतिमा डाउनलोड करा",
- "closeViewer": "इंटरअॅक्टिव्ह व्ह्युअर बंद करा",
- "scalePercentage": "{}%",
- "deleteImageTooltip": "प्रतिमा हटवा"
- }
- }
-},
- "codeBlock": {
- "language": {
- "label": "भाषा",
- "placeholder": "भाषा निवडा",
- "auto": "स्वयंचलित"
- },
- "copyTooltip": "कॉपी करा",
- "searchLanguageHint": "भाषा शोधा",
- "codeCopiedSnackbar": "कोड क्लिपबोर्डवर कॉपी झाला!"
-},
-"inlineLink": {
- "placeholder": "लिंक पेस्ट करा किंवा टाका",
- "openInNewTab": "नवीन टॅबमध्ये उघडा",
- "copyLink": "लिंक कॉपी करा",
- "removeLink": "लिंक काढा",
- "url": {
- "label": "लिंक URL",
- "placeholder": "लिंक URL टाका"
- },
- "title": {
- "label": "लिंक शीर्षक",
- "placeholder": "लिंक शीर्षक टाका"
- }
-},
-"mention": {
- "placeholder": "कोणीतरी, पृष्ठ किंवा तारीख नमूद करा...",
- "page": {
- "label": "पृष्ठाला लिंक करा",
- "tooltip": "पृष्ठ उघडण्यासाठी क्लिक करा"
- },
- "deleted": "हटवले गेले",
- "deletedContent": "ही सामग्री अस्तित्वात नाही किंवा हटवण्यात आली आहे",
- "noAccess": "प्रवेश नाही",
- "deletedPage": "हटवलेले पृष्ठ",
- "trashHint": " - ट्रॅशमध्ये",
- "morePages": "अजून पृष्ठे"
-},
-"toolbar": {
- "resetToDefaultFont": "डीफॉल्ट फॉन्टवर परत जा",
- "textSize": "मजकूराचा आकार",
- "textColor": "मजकूराचा रंग",
- "h1": "मथळा 1",
- "h2": "मथळा 2",
- "h3": "मथळा 3",
- "alignLeft": "डावीकडे संरेखित करा",
- "alignRight": "उजवीकडे संरेखित करा",
- "alignCenter": "मध्यभागी संरेखित करा",
- "link": "लिंक",
- "textAlign": "मजकूर संरेखन",
- "moreOptions": "अधिक पर्याय",
- "font": "फॉन्ट",
- "inlineCode": "इनलाइन कोड",
- "suggestions": "सूचना",
- "turnInto": "मध्ये रूपांतरित करा",
- "equation": "समीकरण",
- "insert": "घाला",
- "linkInputHint": "लिंक पेस्ट करा किंवा पृष्ठे शोधा",
- "pageOrURL": "पृष्ठ किंवा URL",
- "linkName": "लिंकचे नाव",
- "linkNameHint": "लिंकचे नाव प्रविष्ट करा"
-},
-"errorBlock": {
- "theBlockIsNotSupported": "ब्लॉक सामग्री पार्स करण्यात अक्षम",
- "clickToCopyTheBlockContent": "ब्लॉक सामग्री कॉपी करण्यासाठी क्लिक करा",
- "blockContentHasBeenCopied": "ब्लॉक सामग्री कॉपी केली आहे.",
- "parseError": "{} ब्लॉक पार्स करताना त्रुटी आली.",
- "copyBlockContent": "ब्लॉक सामग्री कॉपी करा"
-},
-"mobilePageSelector": {
- "title": "पृष्ठ निवडा",
- "failedToLoad": "पृष्ठ यादी लोड करण्यात अयशस्वी",
- "noPagesFound": "कोणतीही पृष्ठे सापडली नाहीत"
-},
-"attachmentMenu": {
- "choosePhoto": "फोटो निवडा",
- "takePicture": "फोटो काढा",
- "chooseFile": "फाईल निवडा"
- }
- },
- "board": {
- "column": {
- "label": "स्तंभ",
- "createNewCard": "नवीन",
- "renameGroupTooltip": "गटाचे नाव बदलण्यासाठी क्लिक करा",
- "createNewColumn": "नवीन गट जोडा",
- "addToColumnTopTooltip": "वर नवीन कार्ड जोडा",
- "addToColumnBottomTooltip": "खाली नवीन कार्ड जोडा",
- "renameColumn": "स्तंभाचे नाव बदला",
- "hideColumn": "लपवा",
- "newGroup": "नवीन गट",
- "deleteColumn": "हटवा",
- "deleteColumnConfirmation": "हा गट आणि त्यामधील सर्व कार्ड्स हटवले जातील. तुम्हाला खात्री आहे का?"
- },
- "hiddenGroupSection": {
- "sectionTitle": "लपवलेले गट",
- "collapseTooltip": "लपवलेले गट लपवा",
- "expandTooltip": "लपवलेले गट पाहा"
- },
- "cardDetail": "कार्ड तपशील",
- "cardActions": "कार्ड क्रिया",
- "cardDuplicated": "कार्डची प्रत तयार झाली",
- "cardDeleted": "कार्ड हटवले गेले",
- "showOnCard": "कार्ड तपशिलावर दाखवा",
- "setting": "सेटिंग",
- "propertyName": "गुणधर्माचे नाव",
- "menuName": "बोर्ड",
- "showUngrouped": "गटात नसलेली कार्ड्स दाखवा",
- "ungroupedButtonText": "गट नसलेली",
- "ungroupedButtonTooltip": "ज्या कार्ड्स कोणत्याही गटात नाहीत",
- "ungroupedItemsTitle": "बोर्डमध्ये जोडण्यासाठी क्लिक करा",
- "groupBy": "या आधारावर गट करा",
- "groupCondition": "गट स्थिती",
- "referencedBoardPrefix": "याचे दृश्य",
- "notesTooltip": "नोट्स आहेत",
- "mobile": {
- "editURL": "URL संपादित करा",
- "showGroup": "गट दाखवा",
- "showGroupContent": "हा गट बोर्डवर दाखवायचा आहे का?",
- "failedToLoad": "बोर्ड दृश्य लोड होण्यात अयशस्वी"
- },
- "dateCondition": {
- "weekOf": "{} - {} ची आठवडा",
- "today": "आज",
- "yesterday": "काल",
- "tomorrow": "उद्या",
- "lastSevenDays": "शेवटचे ७ दिवस",
- "nextSevenDays": "पुढील ७ दिवस",
- "lastThirtyDays": "शेवटचे ३० दिवस",
- "nextThirtyDays": "पुढील ३० दिवस"
- },
- "noGroup": "गटासाठी कोणताही गुणधर्म निवडलेला नाही",
- "noGroupDesc": "बोर्ड दृश्य दाखवण्यासाठी गट करण्याचा एक गुणधर्म आवश्यक आहे",
- "media": {
- "cardText": "{} {}",
- "fallbackName": "फायली"
- }
-},
- "calendar": {
- "menuName": "कॅलेंडर",
- "defaultNewCalendarTitle": "नाव नाही",
- "newEventButtonTooltip": "नवीन इव्हेंट जोडा",
- "navigation": {
- "today": "आज",
- "jumpToday": "आजवर जा",
- "previousMonth": "मागील महिना",
- "nextMonth": "पुढील महिना",
- "views": {
- "day": "दिवस",
- "week": "आठवडा",
- "month": "महिना",
- "year": "वर्ष"
- }
- },
- "mobileEventScreen": {
- "emptyTitle": "सध्या कोणतेही इव्हेंट नाहीत",
- "emptyBody": "या दिवशी इव्हेंट तयार करण्यासाठी प्लस बटणावर क्लिक करा."
- },
- "settings": {
- "showWeekNumbers": "आठवड्याचे क्रमांक दाखवा",
- "showWeekends": "सप्ताहांत दाखवा",
- "firstDayOfWeek": "आठवड्याची सुरुवात",
- "layoutDateField": "कॅलेंडर मांडणी दिनांकानुसार",
- "changeLayoutDateField": "मांडणी फील्ड बदला",
- "noDateTitle": "तारीख नाही",
- "noDateHint": {
- "zero": "नियोजित नसलेली इव्हेंट्स येथे दिसतील",
- "one": "{count} नियोजित नसलेली इव्हेंट",
- "other": "{count} नियोजित नसलेल्या इव्हेंट्स"
- },
- "unscheduledEventsTitle": "नियोजित नसलेल्या इव्हेंट्स",
- "clickToAdd": "कॅलेंडरमध्ये जोडण्यासाठी क्लिक करा",
- "name": "कॅलेंडर सेटिंग्ज",
- "clickToOpen": "रेकॉर्ड उघडण्यासाठी क्लिक करा"
- },
- "referencedCalendarPrefix": "याचे दृश्य",
- "quickJumpYear": "या वर्षावर जा",
- "duplicateEvent": "इव्हेंट डुप्लिकेट करा"
-},
- "errorDialog": {
- "title": "@:appName त्रुटी",
- "howToFixFallback": "या गैरसोयीबद्दल आम्ही दिलगीर आहोत! कृपया GitHub पेजवर त्रुटीबद्दल माहिती देणारे एक issue सबमिट करा.",
- "howToFixFallbackHint1": "या गैरसोयीबद्दल आम्ही दिलगीर आहोत! कृपया ",
- "howToFixFallbackHint2": " पेजवर त्रुटीचे वर्णन करणारे issue सबमिट करा.",
- "github": "GitHub वर पहा"
-},
-"search": {
- "label": "शोध",
- "sidebarSearchIcon": "पृष्ठ शोधा आणि पटकन जा",
- "placeholder": {
- "actions": "कृती शोधा..."
- }
-},
-"message": {
- "copy": {
- "success": "कॉपी झाले!",
- "fail": "कॉपी करू शकत नाही"
- }
-},
-"unSupportBlock": "सध्याचा व्हर्जन या ब्लॉकला समर्थन देत नाही.",
-"views": {
- "deleteContentTitle": "तुम्हाला हे {pageType} हटवायचे आहे का?",
- "deleteContentCaption": "हे {pageType} हटवल्यास, तुम्ही ते trash मधून पुनर्संचयित करू शकता."
-},
- "colors": {
- "custom": "सानुकूल",
- "default": "डीफॉल्ट",
- "red": "लाल",
- "orange": "संत्रा",
- "yellow": "पिवळा",
- "green": "हिरवा",
- "blue": "निळा",
- "purple": "जांभळा",
- "pink": "गुलाबी",
- "brown": "तपकिरी",
- "gray": "करड्या रंगाचा"
-},
- "emoji": {
- "emojiTab": "इमोजी",
- "search": "इमोजी शोधा",
- "noRecent": "अलीकडील कोणतेही इमोजी नाहीत",
- "noEmojiFound": "कोणतेही इमोजी सापडले नाहीत",
- "filter": "फिल्टर",
- "random": "योगायोगाने",
- "selectSkinTone": "त्वचेचा टोन निवडा",
- "remove": "इमोजी काढा",
- "categories": {
- "smileys": "स्मायली आणि भावना",
- "people": "लोक",
- "animals": "प्राणी आणि निसर्ग",
- "food": "अन्न",
- "activities": "क्रिया",
- "places": "स्थळे",
- "objects": "वस्तू",
- "symbols": "चिन्हे",
- "flags": "ध्वज",
- "nature": "निसर्ग",
- "frequentlyUsed": "नेहमी वापरलेले"
- },
- "skinTone": {
- "default": "डीफॉल्ट",
- "light": "हलका",
- "mediumLight": "मध्यम-हलका",
- "medium": "मध्यम",
- "mediumDark": "मध्यम-गडद",
- "dark": "गडद"
- },
- "openSourceIconsFrom": "खुल्या स्रोताचे आयकॉन्स"
-},
- "inlineActions": {
- "noResults": "निकाल नाही",
- "recentPages": "अलीकडील पृष्ठे",
- "pageReference": "पृष्ठ संदर्भ",
- "docReference": "दस्तऐवज संदर्भ",
- "boardReference": "बोर्ड संदर्भ",
- "calReference": "कॅलेंडर संदर्भ",
- "gridReference": "ग्रिड संदर्भ",
- "date": "तारीख",
- "reminder": {
- "groupTitle": "स्मरणपत्र",
- "shortKeyword": "remind"
- },
- "createPage": "\"{}\" उप-पृष्ठ तयार करा"
-},
- "datePicker": {
- "dateTimeFormatTooltip": "सेटिंग्जमध्ये तारीख आणि वेळ फॉरमॅट बदला",
- "dateFormat": "तारीख फॉरमॅट",
- "includeTime": "वेळ समाविष्ट करा",
- "isRange": "शेवटची तारीख",
- "timeFormat": "वेळ फॉरमॅट",
- "clearDate": "तारीख साफ करा",
- "reminderLabel": "स्मरणपत्र",
- "selectReminder": "स्मरणपत्र निवडा",
- "reminderOptions": {
- "none": "काहीही नाही",
- "atTimeOfEvent": "इव्हेंटच्या वेळी",
- "fiveMinsBefore": "५ मिनिटे आधी",
- "tenMinsBefore": "१० मिनिटे आधी",
- "fifteenMinsBefore": "१५ मिनिटे आधी",
- "thirtyMinsBefore": "३० मिनिटे आधी",
- "oneHourBefore": "१ तास आधी",
- "twoHoursBefore": "२ तास आधी",
- "onDayOfEvent": "इव्हेंटच्या दिवशी",
- "oneDayBefore": "१ दिवस आधी",
- "twoDaysBefore": "२ दिवस आधी",
- "oneWeekBefore": "१ आठवडा आधी",
- "custom": "सानुकूल"
- }
-},
- "relativeDates": {
- "yesterday": "काल",
- "today": "आज",
- "tomorrow": "उद्या",
- "oneWeek": "१ आठवडा"
-},
- "notificationHub": {
- "title": "सूचना",
- "mobile": {
- "title": "अपडेट्स"
- },
- "emptyTitle": "सर्व पूर्ण झाले!",
- "emptyBody": "कोणतीही प्रलंबित सूचना किंवा कृती नाहीत. शांततेचा आनंद घ्या.",
- "tabs": {
- "inbox": "इनबॉक्स",
- "upcoming": "आगामी"
- },
- "actions": {
- "markAllRead": "सर्व वाचलेल्या म्हणून चिन्हित करा",
- "showAll": "सर्व",
- "showUnreads": "न वाचलेल्या"
- },
- "filters": {
- "ascending": "आरोही",
- "descending": "अवरोही",
- "groupByDate": "तारीखेनुसार गटबद्ध करा",
- "showUnreadsOnly": "फक्त न वाचलेल्या दाखवा",
- "resetToDefault": "डीफॉल्टवर रीसेट करा"
- }
-},
- "reminderNotification": {
- "title": "स्मरणपत्र",
- "message": "तुम्ही विसरण्याआधी हे तपासण्याचे लक्षात ठेवा!",
- "tooltipDelete": "हटवा",
- "tooltipMarkRead": "वाचले म्हणून चिन्हित करा",
- "tooltipMarkUnread": "न वाचले म्हणून चिन्हित करा"
-},
- "findAndReplace": {
- "find": "शोधा",
- "previousMatch": "मागील जुळणारे",
- "nextMatch": "पुढील जुळणारे",
- "close": "बंद करा",
- "replace": "बदला",
- "replaceAll": "सर्व बदला",
- "noResult": "कोणतेही निकाल नाहीत",
- "caseSensitive": "केस सेंसिटिव्ह",
- "searchMore": "अधिक निकालांसाठी शोधा"
-},
- "error": {
- "weAreSorry": "आम्ही क्षमस्व आहोत",
- "loadingViewError": "हे दृश्य लोड करण्यात अडचण येत आहे. कृपया तुमचे इंटरनेट कनेक्शन तपासा, अॅप रीफ्रेश करा, आणि समस्या कायम असल्यास आमच्याशी संपर्क साधा.",
- "syncError": "इतर डिव्हाइसमधून डेटा सिंक झाला नाही",
- "syncErrorHint": "कृपया हे पृष्ठ शेवटचे जिथे संपादित केले होते त्या डिव्हाइसवर उघडा, आणि मग सध्याच्या डिव्हाइसवर पुन्हा उघडा.",
- "clickToCopy": "एरर कोड कॉपी करण्यासाठी क्लिक करा"
-},
- "editor": {
- "bold": "जाड",
- "bulletedList": "बुलेट यादी",
- "bulletedListShortForm": "बुलेट",
- "checkbox": "चेकबॉक्स",
- "embedCode": "कोड एम्बेड करा",
- "heading1": "H1",
- "heading2": "H2",
- "heading3": "H3",
- "highlight": "हायलाइट",
- "color": "रंग",
- "image": "प्रतिमा",
- "date": "तारीख",
- "page": "पृष्ठ",
- "italic": "तिरका",
- "link": "लिंक",
- "numberedList": "क्रमांकित यादी",
- "numberedListShortForm": "क्रमांकित",
- "toggleHeading1ShortForm": "Toggle H1",
- "toggleHeading2ShortForm": "Toggle H2",
- "toggleHeading3ShortForm": "Toggle H3",
- "quote": "कोट",
- "strikethrough": "ओढून टाका",
- "text": "मजकूर",
- "underline": "अधोरेखित",
- "fontColorDefault": "डीफॉल्ट",
- "fontColorGray": "धूसर",
- "fontColorBrown": "तपकिरी",
- "fontColorOrange": "केशरी",
- "fontColorYellow": "पिवळा",
- "fontColorGreen": "हिरवा",
- "fontColorBlue": "निळा",
- "fontColorPurple": "जांभळा",
- "fontColorPink": "पिंग",
- "fontColorRed": "लाल",
- "backgroundColorDefault": "डीफॉल्ट पार्श्वभूमी",
- "backgroundColorGray": "धूसर पार्श्वभूमी",
- "backgroundColorBrown": "तपकिरी पार्श्वभूमी",
- "backgroundColorOrange": "केशरी पार्श्वभूमी",
- "backgroundColorYellow": "पिवळी पार्श्वभूमी",
- "backgroundColorGreen": "हिरवी पार्श्वभूमी",
- "backgroundColorBlue": "निळी पार्श्वभूमी",
- "backgroundColorPurple": "जांभळी पार्श्वभूमी",
- "backgroundColorPink": "पिंग पार्श्वभूमी",
- "backgroundColorRed": "लाल पार्श्वभूमी",
- "backgroundColorLime": "लिंबू पार्श्वभूमी",
- "backgroundColorAqua": "पाण्याचा पार्श्वभूमी",
- "done": "पूर्ण",
- "cancel": "रद्द करा",
- "tint1": "टिंट 1",
- "tint2": "टिंट 2",
- "tint3": "टिंट 3",
- "tint4": "टिंट 4",
- "tint5": "टिंट 5",
- "tint6": "टिंट 6",
- "tint7": "टिंट 7",
- "tint8": "टिंट 8",
- "tint9": "टिंट 9",
- "lightLightTint1": "जांभळा",
- "lightLightTint2": "पिंग",
- "lightLightTint3": "फिकट पिंग",
- "lightLightTint4": "केशरी",
- "lightLightTint5": "पिवळा",
- "lightLightTint6": "लिंबू",
- "lightLightTint7": "हिरवा",
- "lightLightTint8": "पाणी",
- "lightLightTint9": "निळा",
- "urlHint": "URL",
- "mobileHeading1": "Heading 1",
- "mobileHeading2": "Heading 2",
- "mobileHeading3": "Heading 3",
- "mobileHeading4": "Heading 4",
- "mobileHeading5": "Heading 5",
- "mobileHeading6": "Heading 6",
- "textColor": "मजकूराचा रंग",
- "backgroundColor": "पार्श्वभूमीचा रंग",
- "addYourLink": "तुमची लिंक जोडा",
- "openLink": "लिंक उघडा",
- "copyLink": "लिंक कॉपी करा",
- "removeLink": "लिंक काढा",
- "editLink": "लिंक संपादित करा",
- "linkText": "मजकूर",
- "linkTextHint": "कृपया मजकूर प्रविष्ट करा",
- "linkAddressHint": "कृपया URL प्रविष्ट करा",
- "highlightColor": "हायलाइट रंग",
- "clearHighlightColor": "हायलाइट काढा",
- "customColor": "स्वतःचा रंग",
- "hexValue": "Hex मूल्य",
- "opacity": "अपारदर्शकता",
- "resetToDefaultColor": "डीफॉल्ट रंगावर रीसेट करा",
- "ltr": "LTR",
- "rtl": "RTL",
- "auto": "स्वयंचलित",
- "cut": "कट",
- "copy": "कॉपी",
- "paste": "पेस्ट",
- "find": "शोधा",
- "select": "निवडा",
- "selectAll": "सर्व निवडा",
- "previousMatch": "मागील जुळणारे",
- "nextMatch": "पुढील जुळणारे",
- "closeFind": "बंद करा",
- "replace": "बदला",
- "replaceAll": "सर्व बदला",
- "regex": "Regex",
- "caseSensitive": "केस सेंसिटिव्ह",
- "uploadImage": "प्रतिमा अपलोड करा",
- "urlImage": "URL प्रतिमा",
- "incorrectLink": "चुकीची लिंक",
- "upload": "अपलोड",
- "chooseImage": "प्रतिमा निवडा",
- "loading": "लोड करत आहे",
- "imageLoadFailed": "प्रतिमा लोड करण्यात अयशस्वी",
- "divider": "विभाजक",
- "table": "तक्त्याचे स्वरूप",
- "colAddBefore": "यापूर्वी स्तंभ जोडा",
- "rowAddBefore": "यापूर्वी पंक्ती जोडा",
- "colAddAfter": "यानंतर स्तंभ जोडा",
- "rowAddAfter": "यानंतर पंक्ती जोडा",
- "colRemove": "स्तंभ काढा",
- "rowRemove": "पंक्ती काढा",
- "colDuplicate": "स्तंभ डुप्लिकेट",
- "rowDuplicate": "पंक्ती डुप्लिकेट",
- "colClear": "सामग्री साफ करा",
- "rowClear": "सामग्री साफ करा",
- "slashPlaceHolder": "'/' टाइप करा आणि घटक जोडा किंवा टाइप सुरू करा",
- "typeSomething": "काहीतरी लिहा...",
- "toggleListShortForm": "टॉगल",
- "quoteListShortForm": "कोट",
- "mathEquationShortForm": "सूत्र",
- "codeBlockShortForm": "कोड"
-},
- "favorite": {
- "noFavorite": "कोणतेही आवडते पृष्ठ नाही",
- "noFavoriteHintText": "पृष्ठाला डावीकडे स्वाइप करा आणि ते आवडत्या यादीत जोडा",
- "removeFromSidebar": "साइडबारमधून काढा",
- "addToSidebar": "साइडबारमध्ये पिन करा"
-},
-"cardDetails": {
- "notesPlaceholder": "/ टाइप करा ब्लॉक घालण्यासाठी, किंवा टाइप करायला सुरुवात करा"
-},
-"blockPlaceholders": {
- "todoList": "करण्याची यादी",
- "bulletList": "यादी",
- "numberList": "क्रमांकित यादी",
- "quote": "कोट",
- "heading": "मथळा {}"
-},
-"titleBar": {
- "pageIcon": "पृष्ठ चिन्ह",
- "language": "भाषा",
- "font": "फॉन्ट",
- "actions": "क्रिया",
- "date": "तारीख",
- "addField": "फील्ड जोडा",
- "userIcon": "वापरकर्त्याचे चिन्ह"
-},
-"noLogFiles": "कोणतीही लॉग फाइल्स नाहीत",
-"newSettings": {
- "myAccount": {
- "title": "माझे खाते",
- "subtitle": "तुमचा प्रोफाइल सानुकूल करा, खाते सुरक्षा व्यवस्थापित करा, AI कीज पहा किंवा लॉगिन करा.",
- "profileLabel": "खाते नाव आणि प्रोफाइल चित्र",
- "profileNamePlaceholder": "तुमचे नाव प्रविष्ट करा",
- "accountSecurity": "खाते सुरक्षा",
- "2FA": "2-स्टेप प्रमाणीकरण",
- "aiKeys": "AI कीज",
- "accountLogin": "खाते लॉगिन",
- "updateNameError": "नाव अपडेट करण्यात अयशस्वी",
- "updateIconError": "चिन्ह अपडेट करण्यात अयशस्वी",
- "aboutAppFlowy": "@:appName विषयी",
- "deleteAccount": {
- "title": "खाते हटवा",
- "subtitle": "तुमचे खाते आणि सर्व डेटा कायमचे हटवा.",
- "description": "तुमचे खाते कायमचे हटवले जाईल आणि सर्व वर्कस्पेसमधून प्रवेश काढून टाकला जाईल.",
- "deleteMyAccount": "माझे खाते हटवा",
- "dialogTitle": "खाते हटवा",
- "dialogContent1": "तुम्हाला खात्री आहे की तुम्ही तुमचे खाते कायमचे हटवू इच्छिता?",
- "dialogContent2": "ही क्रिया पूर्ववत केली जाऊ शकत नाही. हे सर्व वर्कस्पेसमधून प्रवेश हटवेल, खाजगी वर्कस्पेस मिटवेल, आणि सर्व शेअर्ड वर्कस्पेसमधून काढून टाकेल.",
- "confirmHint1": "कृपया पुष्टीसाठी \"@:newSettings.myAccount.deleteAccount.confirmHint3\" टाइप करा.",
- "confirmHint2": "मला समजले आहे की ही क्रिया अपरिवर्तनीय आहे आणि माझे खाते व सर्व संबंधित डेटा कायमचा हटवला जाईल.",
- "confirmHint3": "DELETE MY ACCOUNT",
- "checkToConfirmError": "हटवण्यासाठी पुष्टी बॉक्स निवडणे आवश्यक आहे",
- "failedToGetCurrentUser": "वर्तमान वापरकर्त्याचा ईमेल मिळवण्यात अयशस्वी",
- "confirmTextValidationFailed": "तुमचा पुष्टी मजकूर \"@:newSettings.myAccount.deleteAccount.confirmHint3\" शी जुळत नाही",
- "deleteAccountSuccess": "खाते यशस्वीरित्या हटवले गेले"
- }
- },
- "workplace": {
- "name": "वर्कस्पेस",
- "title": "वर्कस्पेस सेटिंग्स",
- "subtitle": "तुमचा वर्कस्पेस लुक, थीम, फॉन्ट, मजकूर लेआउट, तारीख, वेळ आणि भाषा सानुकूल करा.",
- "workplaceName": "वर्कस्पेसचे नाव",
- "workplaceNamePlaceholder": "वर्कस्पेसचे नाव टाका",
- "workplaceIcon": "वर्कस्पेस चिन्ह",
- "workplaceIconSubtitle": "एक प्रतिमा अपलोड करा किंवा इमोजी वापरा. हे साइडबार आणि सूचना मध्ये दर्शवले जाईल.",
- "renameError": "वर्कस्पेसचे नाव बदलण्यात अयशस्वी",
- "updateIconError": "चिन्ह अपडेट करण्यात अयशस्वी",
- "chooseAnIcon": "चिन्ह निवडा",
- "appearance": {
- "name": "दृश्यरूप",
- "themeMode": {
- "auto": "स्वयंचलित",
- "light": "प्रकाश मोड",
- "dark": "गडद मोड"
- },
- "language": "भाषा"
- }
- },
- "syncState": {
- "syncing": "सिंक्रोनायझ करत आहे",
- "synced": "सिंक्रोनायझ झाले",
- "noNetworkConnected": "नेटवर्क कनेक्ट केलेले नाही"
- }
-},
- "pageStyle": {
- "title": "पृष्ठ शैली",
- "layout": "लेआउट",
- "coverImage": "मुखपृष्ठ प्रतिमा",
- "pageIcon": "पृष्ठ चिन्ह",
- "colors": "रंग",
- "gradient": "ग्रेडियंट",
- "backgroundImage": "पार्श्वभूमी प्रतिमा",
- "presets": "पूर्वनियोजित",
- "photo": "फोटो",
- "unsplash": "Unsplash",
- "pageCover": "पृष्ठ कव्हर",
- "none": "काही नाही",
- "openSettings": "सेटिंग्स उघडा",
- "photoPermissionTitle": "@:appName तुमच्या फोटो लायब्ररीमध्ये प्रवेश करू इच्छित आहे",
- "photoPermissionDescription": "तुमच्या कागदपत्रांमध्ये प्रतिमा जोडण्यासाठी @:appName ला तुमच्या फोटोंमध्ये प्रवेश आवश्यक आहे",
- "cameraPermissionTitle": "@:appName तुमच्या कॅमेऱ्याला प्रवेश करू इच्छित आहे",
- "cameraPermissionDescription": "कॅमेऱ्यातून प्रतिमा जोडण्यासाठी @:appName ला तुमच्या कॅमेऱ्याचा प्रवेश आवश्यक आहे",
- "doNotAllow": "परवानगी देऊ नका",
- "image": "प्रतिमा"
-},
-"commandPalette": {
- "placeholder": "शोधा किंवा प्रश्न विचारा...",
- "bestMatches": "सर्वोत्तम जुळवणी",
- "recentHistory": "अलीकडील इतिहास",
- "navigateHint": "नेव्हिगेट करण्यासाठी",
- "loadingTooltip": "आम्ही निकाल शोधत आहोत...",
- "betaLabel": "बेटा",
- "betaTooltip": "सध्या आम्ही फक्त दस्तऐवज आणि पृष्ठ शोध समर्थन करतो",
- "fromTrashHint": "कचरापेटीतून",
- "noResultsHint": "आपण जे शोधत आहात ते सापडले नाही, कृपया दुसरा शब्द वापरून शोधा.",
- "clearSearchTooltip": "शोध फील्ड साफ करा"
-},
-"space": {
- "delete": "हटवा",
- "deleteConfirmation": "हटवा: ",
- "deleteConfirmationDescription": "या स्पेसमधील सर्व पृष्ठे हटवली जातील आणि कचरापेटीत टाकली जातील, आणि प्रकाशित पृष्ठे अनपब्लिश केली जातील.",
- "rename": "स्पेसचे नाव बदला",
- "changeIcon": "चिन्ह बदला",
- "manage": "स्पेस व्यवस्थापित करा",
- "addNewSpace": "स्पेस तयार करा",
- "collapseAllSubPages": "सर्व उपपृष्ठे संकुचित करा",
- "createNewSpace": "नवीन स्पेस तयार करा",
- "createSpaceDescription": "तुमचे कार्य अधिक चांगल्या प्रकारे आयोजित करण्यासाठी अनेक सार्वजनिक व खाजगी स्पेस तयार करा.",
- "spaceName": "स्पेसचे नाव",
- "spaceNamePlaceholder": "उदा. मार्केटिंग, अभियांत्रिकी, HR",
- "permission": "स्पेस परवानगी",
- "publicPermission": "सार्वजनिक",
- "publicPermissionDescription": "पूर्ण प्रवेशासह सर्व वर्कस्पेस सदस्य",
- "privatePermission": "खाजगी",
- "privatePermissionDescription": "फक्त तुम्हाला या स्पेसमध्ये प्रवेश आहे",
- "spaceIconBackground": "पार्श्वभूमीचा रंग",
- "spaceIcon": "चिन्ह",
- "dangerZone": "धोकादायक क्षेत्र",
- "unableToDeleteLastSpace": "शेवटची स्पेस हटवता येणार नाही",
- "unableToDeleteSpaceNotCreatedByYou": "तुमच्याद्वारे तयार न केलेली स्पेस हटवता येणार नाही",
- "enableSpacesForYourWorkspace": "तुमच्या वर्कस्पेससाठी स्पेस सक्षम करा",
- "title": "स्पेसेस",
- "defaultSpaceName": "सामान्य",
- "upgradeSpaceTitle": "स्पेस सक्षम करा",
- "upgradeSpaceDescription": "तुमच्या वर्कस्पेसचे अधिक चांगल्या प्रकारे व्यवस्थापन करण्यासाठी अनेक सार्वजनिक आणि खाजगी स्पेस तयार करा.",
- "upgrade": "अपग्रेड",
- "upgradeYourSpace": "अनेक स्पेस तयार करा",
- "quicklySwitch": "पुढील स्पेसवर पटकन स्विच करा",
- "duplicate": "स्पेस डुप्लिकेट करा",
- "movePageToSpace": "पृष्ठ स्पेसमध्ये हलवा",
- "cannotMovePageToDatabase": "पृष्ठ डेटाबेसमध्ये हलवता येणार नाही",
- "switchSpace": "स्पेस स्विच करा",
- "spaceNameCannotBeEmpty": "स्पेसचे नाव रिकामे असू शकत नाही",
- "success": {
- "deleteSpace": "स्पेस यशस्वीरित्या हटवली",
- "renameSpace": "स्पेसचे नाव यशस्वीरित्या बदलले",
- "duplicateSpace": "स्पेस यशस्वीरित्या डुप्लिकेट केली",
- "updateSpace": "स्पेस यशस्वीरित्या अपडेट केली"
- },
- "error": {
- "deleteSpace": "स्पेस हटवण्यात अयशस्वी",
- "renameSpace": "स्पेसचे नाव बदलण्यात अयशस्वी",
- "duplicateSpace": "स्पेस डुप्लिकेट करण्यात अयशस्वी",
- "updateSpace": "स्पेस अपडेट करण्यात अयशस्वी"
- },
- "createSpace": "स्पेस तयार करा",
- "manageSpace": "स्पेस व्यवस्थापित करा",
- "renameSpace": "स्पेसचे नाव बदला",
- "mSpaceIconColor": "स्पेस चिन्हाचा रंग",
- "mSpaceIcon": "स्पेस चिन्ह"
-},
- "publish": {
- "hasNotBeenPublished": "हे पृष्ठ अजून प्रकाशित केलेले नाही",
- "spaceHasNotBeenPublished": "स्पेस प्रकाशित करण्यासाठी समर्थन नाही",
- "reportPage": "पृष्ठाची तक्रार करा",
- "databaseHasNotBeenPublished": "डेटाबेस प्रकाशित करण्यास समर्थन नाही.",
- "createdWith": "यांनी तयार केले",
- "downloadApp": "AppFlowy डाउनलोड करा",
- "copy": {
- "codeBlock": "कोड ब्लॉकची सामग्री क्लिपबोर्डवर कॉपी केली गेली आहे",
- "imageBlock": "प्रतिमा लिंक क्लिपबोर्डवर कॉपी केली गेली आहे",
- "mathBlock": "गणितीय समीकरण क्लिपबोर्डवर कॉपी केले गेले आहे",
- "fileBlock": "फाइल लिंक क्लिपबोर्डवर कॉपी केली गेली आहे"
- },
- "containsPublishedPage": "या पृष्ठात एक किंवा अधिक प्रकाशित पृष्ठे आहेत. तुम्ही पुढे गेल्यास ती अनपब्लिश होतील. तुम्हाला हटवणे चालू ठेवायचे आहे का?",
- "publishSuccessfully": "यशस्वीरित्या प्रकाशित झाले",
- "unpublishSuccessfully": "यशस्वीरित्या अनपब्लिश झाले",
- "publishFailed": "प्रकाशित करण्यात अयशस्वी",
- "unpublishFailed": "अनपब्लिश करण्यात अयशस्वी",
- "noAccessToVisit": "या पृष्ठावर प्रवेश नाही...",
- "createWithAppFlowy": "AppFlowy ने वेबसाइट तयार करा",
- "fastWithAI": "AI सह जलद आणि सोपे.",
- "tryItNow": "आत्ताच वापरून पहा",
- "onlyGridViewCanBePublished": "फक्त Grid view प्रकाशित केला जाऊ शकतो",
- "database": {
- "zero": "{} निवडलेले दृश्य प्रकाशित करा",
- "one": "{} निवडलेली दृश्ये प्रकाशित करा",
- "many": "{} निवडलेली दृश्ये प्रकाशित करा",
- "other": "{} निवडलेली दृश्ये प्रकाशित करा"
- },
- "mustSelectPrimaryDatabase": "प्राथमिक दृश्य निवडणे आवश्यक आहे",
- "noDatabaseSelected": "कोणताही डेटाबेस निवडलेला नाही, कृपया किमान एक डेटाबेस निवडा.",
- "unableToDeselectPrimaryDatabase": "प्राथमिक डेटाबेस अननिवड करता येणार नाही",
- "saveThisPage": "या टेम्पलेटपासून सुरू करा",
- "duplicateTitle": "तुम्हाला हे कुठे जोडायचे आहे",
- "selectWorkspace": "वर्कस्पेस निवडा",
- "addTo": "मध्ये जोडा",
- "duplicateSuccessfully": "तुमच्या वर्कस्पेसमध्ये जोडले गेले",
- "duplicateSuccessfullyDescription": "AppFlowy स्थापित केले नाही? तुम्ही 'डाउनलोड' वर क्लिक केल्यावर डाउनलोड आपोआप सुरू होईल.",
- "downloadIt": "डाउनलोड करा",
- "openApp": "अॅपमध्ये उघडा",
- "duplicateFailed": "डुप्लिकेट करण्यात अयशस्वी",
- "membersCount": {
- "zero": "सदस्य नाहीत",
- "one": "1 सदस्य",
- "many": "{count} सदस्य",
- "other": "{count} सदस्य"
- },
- "useThisTemplate": "हा टेम्पलेट वापरा"
-},
-"web": {
- "continue": "पुढे जा",
- "or": "किंवा",
- "continueWithGoogle": "Google सह पुढे जा",
- "continueWithGithub": "GitHub सह पुढे जा",
- "continueWithDiscord": "Discord सह पुढे जा",
- "continueWithApple": "Apple सह पुढे जा",
- "moreOptions": "अधिक पर्याय",
- "collapse": "आकुंचन",
- "signInAgreement": "\"पुढे जा\" क्लिक करून, तुम्ही AppFlowy च्या अटींना सहमती दिली आहे",
- "signInLocalAgreement": "\"सुरुवात करा\" क्लिक करून, तुम्ही AppFlowy च्या अटींना सहमती दिली आहे",
- "and": "आणि",
- "termOfUse": "वापर अटी",
- "privacyPolicy": "गोपनीयता धोरण",
- "signInError": "साइन इन त्रुटी",
- "login": "साइन अप किंवा लॉग इन करा",
- "fileBlock": {
- "uploadedAt": "{time} रोजी अपलोड केले",
- "linkedAt": "{time} रोजी लिंक जोडली",
- "empty": "फाईल अपलोड करा किंवा एम्बेड करा",
- "uploadFailed": "अपलोड अयशस्वी, कृपया पुन्हा प्रयत्न करा",
- "retry": "पुन्हा प्रयत्न करा"
- },
- "importNotion": "Notion वरून आयात करा",
- "import": "आयात करा",
- "importSuccess": "यशस्वीरित्या अपलोड केले",
- "importSuccessMessage": "आम्ही तुम्हाला आयात पूर्ण झाल्यावर सूचित करू. त्यानंतर, तुम्ही साइडबारमध्ये तुमची आयात केलेली पृष्ठे पाहू शकता.",
- "importFailed": "आयात अयशस्वी, कृपया फाईल फॉरमॅट तपासा",
- "dropNotionFile": "तुमची Notion zip फाईल येथे ड्रॉप करा किंवा ब्राउझ करा",
- "error": {
- "pageNameIsEmpty": "पृष्ठाचे नाव रिकामे आहे, कृपया दुसरे नाव वापरून पहा"
- }
-},
- "globalComment": {
- "comments": "टिप्पण्या",
- "addComment": "टिप्पणी जोडा",
- "reactedBy": "यांनी प्रतिक्रिया दिली",
- "addReaction": "प्रतिक्रिया जोडा",
- "reactedByMore": "आणि {count} इतर",
- "showSeconds": {
- "one": "1 सेकंदापूर्वी",
- "other": "{count} सेकंदांपूर्वी",
- "zero": "आत्ताच",
- "many": "{count} सेकंदांपूर्वी"
- },
- "showMinutes": {
- "one": "1 मिनिटापूर्वी",
- "other": "{count} मिनिटांपूर्वी",
- "many": "{count} मिनिटांपूर्वी"
- },
- "showHours": {
- "one": "1 तासापूर्वी",
- "other": "{count} तासांपूर्वी",
- "many": "{count} तासांपूर्वी"
- },
- "showDays": {
- "one": "1 दिवसापूर्वी",
- "other": "{count} दिवसांपूर्वी",
- "many": "{count} दिवसांपूर्वी"
- },
- "showMonths": {
- "one": "1 महिन्यापूर्वी",
- "other": "{count} महिन्यांपूर्वी",
- "many": "{count} महिन्यांपूर्वी"
- },
- "showYears": {
- "one": "1 वर्षापूर्वी",
- "other": "{count} वर्षांपूर्वी",
- "many": "{count} वर्षांपूर्वी"
- },
- "reply": "उत्तर द्या",
- "deleteComment": "टिप्पणी हटवा",
- "youAreNotOwner": "तुम्ही या टिप्पणीचे मालक नाही",
- "confirmDeleteDescription": "तुम्हाला ही टिप्पणी हटवायची आहे याची खात्री आहे का?",
- "hasBeenDeleted": "हटवले गेले",
- "replyingTo": "याला उत्तर देत आहे",
- "noAccessDeleteComment": "तुम्हाला ही टिप्पणी हटवण्याची परवानगी नाही",
- "collapse": "संकुचित करा",
- "readMore": "अधिक वाचा",
- "failedToAddComment": "टिप्पणी जोडण्यात अयशस्वी",
- "commentAddedSuccessfully": "टिप्पणी यशस्वीरित्या जोडली गेली.",
- "commentAddedSuccessTip": "तुम्ही नुकतीच एक टिप्पणी जोडली किंवा उत्तर दिले आहे. वर जाऊन ताजी टिप्पण्या पाहायच्या का?"
-},
- "template": {
- "asTemplate": "टेम्पलेट म्हणून जतन करा",
- "name": "टेम्पलेट नाव",
- "description": "टेम्पलेट वर्णन",
- "about": "टेम्पलेट माहिती",
- "deleteFromTemplate": "टेम्पलेटमधून हटवा",
- "preview": "टेम्पलेट पूर्वदृश्य",
- "categories": "टेम्पलेट श्रेणी",
- "isNewTemplate": "नवीन टेम्पलेटमध्ये पिन करा",
- "featured": "वैशिष्ट्यीकृतमध्ये पिन करा",
- "relatedTemplates": "संबंधित टेम्पलेट्स",
- "requiredField": "{field} आवश्यक आहे",
- "addCategory": "\"{category}\" जोडा",
- "addNewCategory": "नवीन श्रेणी जोडा",
- "addNewCreator": "नवीन निर्माता जोडा",
- "deleteCategory": "श्रेणी हटवा",
- "editCategory": "श्रेणी संपादित करा",
- "editCreator": "निर्माता संपादित करा",
- "category": {
- "name": "श्रेणीचे नाव",
- "icon": "श्रेणी चिन्ह",
- "bgColor": "श्रेणी पार्श्वभूमीचा रंग",
- "priority": "श्रेणी प्राधान्य",
- "desc": "श्रेणीचे वर्णन",
- "type": "श्रेणी प्रकार",
- "icons": "श्रेणी चिन्हे",
- "colors": "श्रेणी रंग",
- "byUseCase": "वापराच्या आधारे",
- "byFeature": "वैशिष्ट्यांनुसार",
- "deleteCategory": "श्रेणी हटवा",
- "deleteCategoryDescription": "तुम्हाला ही श्रेणी हटवायची आहे का?",
- "typeToSearch": "श्रेणी शोधण्यासाठी टाइप करा..."
- },
- "creator": {
- "label": "टेम्पलेट निर्माता",
- "name": "निर्मात्याचे नाव",
- "avatar": "निर्मात्याचा अवतार",
- "accountLinks": "निर्मात्याचे खाते दुवे",
- "uploadAvatar": "अवतार अपलोड करण्यासाठी क्लिक करा",
- "deleteCreator": "निर्माता हटवा",
- "deleteCreatorDescription": "तुम्हाला हा निर्माता हटवायचा आहे का?",
- "typeToSearch": "निर्माते शोधण्यासाठी टाइप करा..."
- },
- "uploadSuccess": "टेम्पलेट यशस्वीरित्या अपलोड झाले",
- "uploadSuccessDescription": "तुमचे टेम्पलेट यशस्वीरित्या अपलोड झाले आहे. आता तुम्ही ते टेम्पलेट गॅलरीमध्ये पाहू शकता.",
- "viewTemplate": "टेम्पलेट पहा",
- "deleteTemplate": "टेम्पलेट हटवा",
- "deleteSuccess": "टेम्पलेट यशस्वीरित्या हटवले गेले",
- "deleteTemplateDescription": "याचा वर्तमान पृष्ठ किंवा प्रकाशित स्थितीवर परिणाम होणार नाही. तुम्हाला हे टेम्पलेट हटवायचे आहे का?",
- "addRelatedTemplate": "संबंधित टेम्पलेट जोडा",
- "removeRelatedTemplate": "संबंधित टेम्पलेट हटवा",
- "uploadAvatar": "अवतार अपलोड करा",
- "searchInCategory": "{category} मध्ये शोधा",
- "label": "टेम्पलेट्स"
-},
- "fileDropzone": {
- "dropFile": "फाइल अपलोड करण्यासाठी येथे क्लिक करा किंवा ड्रॅग करा",
- "uploading": "अपलोड करत आहे...",
- "uploadFailed": "अपलोड अयशस्वी",
- "uploadSuccess": "अपलोड यशस्वी",
- "uploadSuccessDescription": "फाइल यशस्वीरित्या अपलोड झाली आहे",
- "uploadFailedDescription": "फाइल अपलोड अयशस्वी झाली आहे",
- "uploadingDescription": "फाइल अपलोड होत आहे"
-},
- "gallery": {
- "preview": "पूर्ण स्क्रीनमध्ये उघडा",
- "copy": "कॉपी करा",
- "download": "डाउनलोड",
- "prev": "मागील",
- "next": "पुढील",
- "resetZoom": "झूम रिसेट करा",
- "zoomIn": "झूम इन",
- "zoomOut": "झूम आउट"
-},
- "invitation": {
- "join": "सामील व्हा",
- "on": "वर",
- "invitedBy": "यांनी आमंत्रित केले",
- "membersCount": {
- "zero": "{count} सदस्य",
- "one": "{count} सदस्य",
- "many": "{count} सदस्य",
- "other": "{count} सदस्य"
- },
- "tip": "तुम्हाला खालील माहितीच्या आधारे या कार्यक्षेत्रात सामील होण्यासाठी आमंत्रित करण्यात आले आहे. ही माहिती चुकीची असल्यास, कृपया प्रशासकाशी संपर्क साधा.",
- "joinWorkspace": "वर्कस्पेसमध्ये सामील व्हा",
- "success": "तुम्ही यशस्वीरित्या वर्कस्पेसमध्ये सामील झाला आहात",
- "successMessage": "आता तुम्ही सर्व पृष्ठे आणि कार्यक्षेत्रे वापरू शकता.",
- "openWorkspace": "AppFlowy उघडा",
- "alreadyAccepted": "तुम्ही आधीच आमंत्रण स्वीकारले आहे",
- "errorModal": {
- "title": "काहीतरी चुकले आहे",
- "description": "तुमचे सध्याचे खाते {email} कदाचित या वर्कस्पेसमध्ये प्रवेशासाठी पात्र नाही. कृपया योग्य खात्याने लॉग इन करा किंवा वर्कस्पेस मालकाशी संपर्क साधा.",
- "contactOwner": "मालकाशी संपर्क करा",
- "close": "मुख्यपृष्ठावर परत जा",
- "changeAccount": "खाते बदला"
- }
-},
- "requestAccess": {
- "title": "या पृष्ठासाठी प्रवेश नाही",
- "subtitle": "तुम्ही या पृष्ठाच्या मालकाकडून प्रवेशासाठी विनंती करू शकता. मंजुरीनंतर तुम्हाला हे पृष्ठ पाहता येईल.",
- "requestAccess": "प्रवेशाची विनंती करा",
- "backToHome": "मुख्यपृष्ठावर परत जा",
- "tip": "तुम्ही सध्या म्हणून लॉग इन आहात.",
- "mightBe": "कदाचित तुम्हाला दुसऱ्या खात्याने लॉग इन करणे आवश्यक आहे.",
- "successful": "विनंती यशस्वीपणे पाठवली गेली",
- "successfulMessage": "मालकाने मंजुरी दिल्यावर तुम्हाला सूचित केले जाईल.",
- "requestError": "प्रवेशाची विनंती अयशस्वी",
- "repeatRequestError": "तुम्ही यासाठी आधीच विनंती केली आहे"
-},
- "approveAccess": {
- "title": "वर्कस्पेसमध्ये सामील होण्यासाठी विनंती मंजूर करा",
- "requestSummary": " यांनी मध्ये सामील होण्यासाठी आणि पाहण्यासाठी विनंती केली आहे",
- "upgrade": "अपग्रेड",
- "downloadApp": "AppFlowy डाउनलोड करा",
- "approveButton": "मंजूर करा",
- "approveSuccess": "मंजूर यशस्वी",
- "approveError": "मंजुरी अयशस्वी. कृपया वर्कस्पेस मर्यादा ओलांडलेली नाही याची खात्री करा",
- "getRequestInfoError": "विनंतीची माहिती मिळवण्यात अयशस्वी",
- "memberCount": {
- "zero": "कोणतेही सदस्य नाहीत",
- "one": "1 सदस्य",
- "many": "{count} सदस्य",
- "other": "{count} सदस्य"
- },
- "alreadyProTitle": "वर्कस्पेस योजना मर्यादा गाठली आहे",
- "alreadyProMessage": "अधिक सदस्यांसाठी शी संपर्क साधा",
- "repeatApproveError": "तुम्ही ही विनंती आधीच मंजूर केली आहे",
- "ensurePlanLimit": "कृपया खात्री करा की योजना मर्यादा ओलांडलेली नाही. जर ओलांडली असेल तर वर्कस्पेस योजना करा किंवा करा.",
- "requestToJoin": "मध्ये सामील होण्यासाठी विनंती केली",
- "asMember": "सदस्य म्हणून"
-},
- "upgradePlanModal": {
- "title": "Pro प्लॅनवर अपग्रेड करा",
- "message": "{name} ने फ्री सदस्य मर्यादा गाठली आहे. अधिक सदस्य आमंत्रित करण्यासाठी Pro प्लॅनवर अपग्रेड करा.",
- "upgradeSteps": "AppFlowy वर तुमची योजना कशी अपग्रेड करावी:",
- "step1": "1. सेटिंग्जमध्ये जा",
- "step2": "2. 'योजना' वर क्लिक करा",
- "step3": "3. 'योजना बदला' निवडा",
- "appNote": "नोंद:",
- "actionButton": "अपग्रेड करा",
- "downloadLink": "अॅप डाउनलोड करा",
- "laterButton": "नंतर",
- "refreshNote": "यशस्वी अपग्रेडनंतर, तुमची नवीन वैशिष्ट्ये सक्रिय करण्यासाठी वर क्लिक करा.",
- "refresh": "येथे"
-},
- "breadcrumbs": {
- "label": "ब्रेडक्रम्स"
-},
- "time": {
- "justNow": "आत्ताच",
- "seconds": {
- "one": "1 सेकंद",
- "other": "{count} सेकंद"
- },
- "minutes": {
- "one": "1 मिनिट",
- "other": "{count} मिनिटे"
- },
- "hours": {
- "one": "1 तास",
- "other": "{count} तास"
- },
- "days": {
- "one": "1 दिवस",
- "other": "{count} दिवस"
- },
- "weeks": {
- "one": "1 आठवडा",
- "other": "{count} आठवडे"
- },
- "months": {
- "one": "1 महिना",
- "other": "{count} महिने"
- },
- "years": {
- "one": "1 वर्ष",
- "other": "{count} वर्षे"
- },
- "ago": "पूर्वी",
- "yesterday": "काल",
- "today": "आज"
-},
- "members": {
- "zero": "सदस्य नाहीत",
- "one": "1 सदस्य",
- "many": "{count} सदस्य",
- "other": "{count} सदस्य"
-},
- "tabMenu": {
- "close": "बंद करा",
- "closeDisabledHint": "पिन केलेले टॅब बंद करता येत नाही, कृपया आधी अनपिन करा",
- "closeOthers": "इतर टॅब बंद करा",
- "closeOthersHint": "हे सर्व अनपिन केलेले टॅब्स बंद करेल या टॅब वगळता",
- "closeOthersDisabledHint": "सर्व टॅब पिन केलेले आहेत, बंद करण्यासाठी कोणतेही टॅब सापडले नाहीत",
- "favorite": "आवडते",
- "unfavorite": "आवडते काढा",
- "favoriteDisabledHint": "हे दृश्य आवडते म्हणून जतन करता येत नाही",
- "pinTab": "पिन करा",
- "unpinTab": "अनपिन करा"
-},
- "openFileMessage": {
- "success": "फाइल यशस्वीरित्या उघडली",
- "fileNotFound": "फाइल सापडली नाही",
- "noAppToOpenFile": "ही फाइल उघडण्यासाठी कोणतेही अॅप उपलब्ध नाही",
- "permissionDenied": "ही फाइल उघडण्यासाठी परवानगी नाही",
- "unknownError": "फाइल उघडण्यात अयशस्वी"
-},
- "inviteMember": {
- "requestInviteMembers": "तुमच्या वर्कस्पेसमध्ये आमंत्रित करा",
- "inviteFailedMemberLimit": "सदस्य मर्यादा गाठली आहे, कृपया ",
- "upgrade": "अपग्रेड करा",
- "addEmail": "email@example.com, email2@example.com...",
- "requestInvites": "आमंत्रण पाठवा",
- "inviteAlready": "तुम्ही या ईमेलला आधीच आमंत्रित केले आहे: {email}",
- "inviteSuccess": "आमंत्रण यशस्वीपणे पाठवले",
- "description": "खाली ईमेल कॉमा वापरून टाका. शुल्क सदस्य संख्येवर आधारित असते.",
- "emails": "ईमेल"
-},
- "quickNote": {
- "label": "झटपट नोंद",
- "quickNotes": "झटपट नोंदी",
- "search": "झटपट नोंदी शोधा",
- "collapseFullView": "पूर्ण दृश्य लपवा",
- "expandFullView": "पूर्ण दृश्य उघडा",
- "createFailed": "झटपट नोंद तयार करण्यात अयशस्वी",
- "quickNotesEmpty": "कोणत्याही झटपट नोंदी नाहीत",
- "emptyNote": "रिकामी नोंद",
- "deleteNotePrompt": "निवडलेली नोंद कायमची हटवली जाईल. तुम्हाला नक्की ती हटवायची आहे का?",
- "addNote": "नवीन नोंद",
- "noAdditionalText": "अधिक माहिती नाही"
-},
- "subscribe": {
- "upgradePlanTitle": "योजना तुलना करा आणि निवडा",
- "yearly": "वार्षिक",
- "save": "{discount}% बचत",
- "monthly": "मासिक",
- "priceIn": "किंमत येथे: ",
- "free": "फ्री",
- "pro": "प्रो",
- "freeDescription": "2 सदस्यांपर्यंत वैयक्तिक वापरकर्त्यांसाठी सर्व काही आयोजित करण्यासाठी",
- "proDescription": "प्रकल्प आणि टीम नॉलेज व्यवस्थापित करण्यासाठी लहान टीमसाठी",
- "proDuration": {
- "monthly": "प्रति सदस्य प्रति महिना\nमासिक बिलिंग",
- "yearly": "प्रति सदस्य प्रति महिना\nवार्षिक बिलिंग"
- },
- "cancel": "खालच्या योजनेवर जा",
- "changePlan": "प्रो योजनेवर अपग्रेड करा",
- "everythingInFree": "फ्री योजनेतील सर्व काही +",
- "currentPlan": "सध्याची योजना",
- "freeDuration": "कायम",
- "freePoints": {
- "first": "1 सहकार्यात्मक वर्कस्पेस (2 सदस्यांपर्यंत)",
- "second": "अमर्यादित पृष्ठे आणि ब्लॉक्स",
- "three": "5 GB संचयन",
- "four": "बुद्धिमान शोध",
- "five": "20 AI प्रतिसाद",
- "six": "मोबाईल अॅप",
- "seven": "रिअल-टाइम सहकार्य"
- },
- "proPoints": {
- "first": "अमर्यादित संचयन",
- "second": "10 वर्कस्पेस सदस्यांपर्यंत",
- "three": "अमर्यादित AI प्रतिसाद",
- "four": "अमर्यादित फाइल अपलोड्स",
- "five": "कस्टम नेमस्पेस"
- },
- "cancelPlan": {
- "title": "आपल्याला जाताना पाहून वाईट वाटते",
- "success": "आपली सदस्यता यशस्वीरित्या रद्द झाली आहे",
- "description": "आपल्याला जाताना वाईट वाटते. कृपया आम्हाला सुधारण्यासाठी तुमचे अभिप्राय कळवा. खालील काही प्रश्नांना उत्तर द्या.",
- "commonOther": "इतर",
- "otherHint": "आपले उत्तर येथे लिहा",
- "questionOne": {
- "question": "तुम्ही AppFlowy Pro सदस्यता का रद्द केली?",
- "answerOne": "खर्च खूप जास्त आहे",
- "answerTwo": "वैशिष्ट्ये अपेक्षेनुसार नव्हती",
- "answerThree": "चांगला पर्याय सापडला",
- "answerFour": "खर्च योग्य ठरण्यासाठी वापर पुरेसा नव्हता",
- "answerFive": "सेवा समस्या किंवा तांत्रिक अडचणी"
- },
- "questionTwo": {
- "question": "तुम्ही भविष्यात AppFlowy Pro पुन्हा घेण्याची शक्यता किती आहे?",
- "answerOne": "खूप शक्यता आहे",
- "answerTwo": "काहीशी शक्यता आहे",
- "answerThree": "निश्चित नाही",
- "answerFour": "अल्प शक्यता आहे",
- "answerFive": "शक्यता नाही"
- },
- "questionThree": {
- "question": "सदस्यतेदरम्यान कोणते Pro फिचर तुम्हाला सर्वात जास्त उपयोगी वाटले?",
- "answerOne": "मल्टी-यूजर सहकार्य",
- "answerTwo": "लांब कालावधीसाठी आवृत्ती इतिहास",
- "answerThree": "अमर्यादित AI प्रतिसाद",
- "answerFour": "स्थानिक AI मॉडेल्सचा प्रवेश"
- },
- "questionFour": {
- "question": "AppFlowy बाबत तुमचा एकूण अनुभव कसा होता?",
- "answerOne": "छान",
- "answerTwo": "चांगला",
- "answerThree": "सामान्य",
- "answerFour": "थोडासा वाईट",
- "answerFive": "असंतोषजनक"
- }
- }
-},
- "ai": {
- "contentPolicyViolation": "संवेदनशील सामग्रीमुळे प्रतिमा निर्मिती अयशस्वी झाली. कृपया तुमचे इनपुट पुन्हा लिहा आणि पुन्हा प्रयत्न करा.",
- "textLimitReachedDescription": "तुमच्या वर्कस्पेसमध्ये मोफत AI प्रतिसाद संपले आहेत. अनलिमिटेड प्रतिसादांसाठी प्रो योजना घेण्याचा किंवा AI अॅड-ऑन खरेदी करण्याचा विचार करा.",
- "imageLimitReachedDescription": "तुमचे मोफत AI प्रतिमा कोटा संपले आहे. कृपया प्रो योजना घ्या किंवा AI अॅड-ऑन खरेदी करा.",
- "limitReachedAction": {
- "textDescription": "तुमच्या वर्कस्पेसमध्ये मोफत AI प्रतिसाद संपले आहेत. अधिक प्रतिसाद मिळवण्यासाठी कृपया",
- "imageDescription": "तुमचे मोफत AI प्रतिमा कोटा संपले आहे. कृपया",
- "upgrade": "अपग्रेड करा",
- "toThe": "या योजनेवर",
- "proPlan": "प्रो योजना",
- "orPurchaseAn": "किंवा खरेदी करा",
- "aiAddon": "AI अॅड-ऑन"
- },
- "editing": "संपादन करत आहे",
- "analyzing": "विश्लेषण करत आहे",
- "continueWritingEmptyDocumentTitle": "लेखन सुरू ठेवता आले नाही",
- "continueWritingEmptyDocumentDescription": "तुमच्या दस्तऐवजातील मजकूर वाढवण्यात अडचण येत आहे. कृपया एक छोटं परिचय लिहा, मग आम्ही पुढे नेऊ!",
- "more": "अधिक"
-},
- "autoUpdate": {
- "criticalUpdateTitle": "अद्यतन आवश्यक आहे",
- "criticalUpdateDescription": "तुमचा अनुभव सुधारण्यासाठी आम्ही सुधारणा केल्या आहेत! कृपया {currentVersion} वरून {newVersion} वर अद्यतन करा.",
- "criticalUpdateButton": "अद्यतन करा",
- "bannerUpdateTitle": "नवीन आवृत्ती उपलब्ध!",
- "bannerUpdateDescription": "नवीन वैशिष्ट्ये आणि सुधारणांसाठी. अद्यतनासाठी 'Update' क्लिक करा.",
- "bannerUpdateButton": "अद्यतन करा",
- "settingsUpdateTitle": "नवीन आवृत्ती ({newVersion}) उपलब्ध!",
- "settingsUpdateDescription": "सध्याची आवृत्ती: {currentVersion} (अधिकृत बिल्ड) → {newVersion}",
- "settingsUpdateButton": "अद्यतन करा",
- "settingsUpdateWhatsNew": "काय नवीन आहे"
-},
- "lockPage": {
- "lockPage": "लॉक केलेले",
- "reLockPage": "पुन्हा लॉक करा",
- "lockTooltip": "अनवधानाने संपादन टाळण्यासाठी पृष्ठ लॉक केले आहे. अनलॉक करण्यासाठी क्लिक करा.",
- "pageLockedToast": "पृष्ठ लॉक केले आहे. कोणी अनलॉक करेपर्यंत संपादन अक्षम आहे.",
- "lockedOperationTooltip": "पृष्ठ अनवधानाने संपादनापासून वाचवण्यासाठी लॉक केले आहे."
-},
- "suggestion": {
- "accept": "स्वीकारा",
- "keep": "जसे आहे तसे ठेवा",
- "discard": "रद्द करा",
- "close": "बंद करा",
- "tryAgain": "पुन्हा प्रयत्न करा",
- "rewrite": "पुन्हा लिहा",
- "insertBelow": "खाली टाका"
-}
-}
diff --git a/frontend/appflowy_flutter/dsa_priv.pem b/frontend/appflowy_flutter/dsa_priv.pem
new file mode 100644
index 0000000000..66843054b7
--- /dev/null
+++ b/frontend/appflowy_flutter/dsa_priv.pem
@@ -0,0 +1,26 @@
+-----BEGIN PRIVATE KEY-----
+MIIEXAIBADCCBDUGByqGSM44BAEwggQoAoICAQDlkozRmUnVH1MJFqOamAmUYu0Y
+ruaTrrt6rCIZ0LFrfNnmHA4LOQEcXwBTTyn5sBmkPq+lb/rjmERKhmvl1rfo6q7t
+J8mG4TWqSu0tOJQ6QxexnNW4yhzK/r9MS5MQus4Al+y2hQLaAMOUIOnaWIrC9OHy
+7xyw+sVipECVKyQqipS4shGUSqbcN+ocQuTB+I0MtIjBii0DGSEY3pxQrfNWjHFL
+7iTVKiTn3YOWPJQvv3FvEDrN+5xU5JZpD97ZhXaJpLUyOQaNvcPaOELPWcOSJwqH
+Opf5b5N/VZ8SGbHNdxy9d5sSChBgtuAOihEhSp6SjFQ9eVHOf4NyJwSEMmi0gpdp
+qm4ZQRJUnM2zIi0p9twR9FRYXzrxOs6yGCQEY+xFG93ShTLTj3zMrIyFqBsqEwFy
+JiJWYWe/zp0V7UlLP+jXO9u9eghNmly7QVqD2P0qs/1V0jZFRuLWpsv4inau/qMZ
+5EhGG4xCJZXfN1pkehy6e05/h+vs5anK3Wa/H8AtY6cK4CpzAanELvn3AH7VLbAh
+Lswu6d5CV+DoFgxCWMzGBSdmCYU+2wRLaL8Q9TZHDR+pvQlunEFdfFoGES9WjBPh
+AsVA6Mq22U8XSje9yHI3X9Eqe/7a+ajSgcGmB7oQ11+4xf5h2PtubRW/JL0KMjxC
+xMTpq1md6Ndx/ptBUwIdAIOyiKb2YcTLWAOt+LAlRXMsY1+W4pTXJfV6RcMCggIA
+Pxbd0HNj2O/aQhJxNZDMBIcx6+cZ+LKch7qLcaEpVqWHvDSnR2eOJJzWn0RoKK+V
+uix/4T8texSQkWxAeFFdo6kyrR9XNL7hqEFFq8o9VpmvRzvG6h/bBgh3AHAQE3p/
+8WrbK13IhnlWqd0MjFufSphm63o0gaWl95j+6KeUoKQnioetu9HiMtFKx0d/KYqT
+QJg7hvR6VNCU2oShfXR3ce7RnUYwD37+djrUjUkoAZkZq2KoxBiKyeoSIeqAme19
+tKcOs6b17mhALELuJ+NtDwlDunyiCDUYX9lTPijHwKeIFtBs38+OtRk3aIqmWTQd
+bsCzAxp+kUMA5ESBME/RBNCSPHuDvtA3wfWvNbA5DXfZLwCgNSxhekq8XntIsRzf
+J4v4uPzKFcVM3+sUUfSF04HHC9ol+PpLqXUyMnskiizqxFPq7H+6tyFZ7X2HiG6T
+jcfVWthmv+JyfcABjVnk2qFH7GagENbdtYmfUox13LhE59Sh5chaJnCFtCDp8NCl
+WgZnixCOFQ9EgTLaH6MovTvWpEgG2MfBCu5SMUHi2qSflorqpRFH+rA7NZSnyz3w
+m7NB+fJSOP0IjEkOh7MafU6Z61oK9WY/Fc+F1zIENVv8PUc3p75y/4RAp4xzyKcT
+ilaNC9U/3MRr3QmWwY7ejtZx6xdOxsvWBRDRSNbDdIkEHgIcfy0+ZHp+4MBcWSDv
+uzWeM8QmNvbP+owM+H4F7A==
+-----END PRIVATE KEY-----
diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart
index e34ac02aab..230ee59495 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart
@@ -15,6 +15,7 @@ void main() {
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
+ await tester.tapContinousAnotherWay();
await tester.tapAnonymousSignInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
diff --git a/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart b/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart
index 0b77a0167b..3271070c74 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart
@@ -6,7 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart';
-import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_cell.dart';
+import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
@@ -44,12 +44,12 @@ void main() {
await tester.pumpAndSettle(const Duration(milliseconds: 200));
// Expect two search results "ViewOna" and "ViewOne" (Distance 1 to ViewOna)
- expect(find.byType(SearchResultCell), findsNWidgets(2));
+ expect(find.byType(SearchResultTile), findsNWidgets(2));
// The score should be higher for "ViewOna" thus it should be shown first
final secondDocumentWidget = tester
- .widget(find.byType(SearchResultCell).first) as SearchResultCell;
- expect(secondDocumentWidget.item.displayName, secondDocument);
+ .widget(find.byType(SearchResultTile).first) as SearchResultTile;
+ expect(secondDocumentWidget.result.data, secondDocument);
// Change search to "ViewOne"
await tester.enterText(searchFieldFinder, firstDocument);
@@ -57,9 +57,9 @@ void main() {
// The score should be higher for "ViewOne" thus it should be shown first
final firstDocumentWidget = tester.widget(
- find.byType(SearchResultCell).first,
- ) as SearchResultCell;
- expect(firstDocumentWidget.item.displayName, firstDocument);
+ find.byType(SearchResultTile).first,
+ ) as SearchResultTile;
+ expect(firstDocumentWidget.result.data, firstDocument);
});
testWidgets('Displaying icons in search results', (tester) async {
@@ -89,11 +89,11 @@ void main() {
);
await tester.enterText(searchFieldFinder, 'Page-$randomValue');
await tester.pumpAndSettle(const Duration(milliseconds: 200));
- expect(find.byType(SearchResultCell), findsNWidgets(2));
+ expect(find.byType(SearchResultTile), findsNWidgets(2));
/// check results
final svgs = find.descendant(
- of: find.byType(SearchResultCell),
+ of: find.byType(SearchResultTile),
matching: find.byType(FlowySvg),
);
expect(svgs, findsNWidgets(2));
diff --git a/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart b/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart
index b9495ae0e7..277ae8f21e 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart
@@ -1,5 +1,5 @@
import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart';
-import 'package:appflowy/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart';
+import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart';
import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_views_list.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -27,12 +27,11 @@ void main() {
expect(find.byType(RecentViewsList), findsOneWidget);
// Expect three recent history items
- expect(find.byType(SearchRecentViewCell), findsNWidgets(3));
+ expect(find.byType(RecentViewTile), findsNWidgets(3));
// Expect the first item to be the last viewed document
final firstDocumentWidget =
- tester.widget(find.byType(SearchRecentViewCell).first)
- as SearchRecentViewCell;
+ tester.widget(find.byType(RecentViewTile).first) as RecentViewTile;
expect(firstDocumentWidget.view.name, secondDocument);
});
});
diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart
index a71110f1e0..9b9434d3d7 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart
@@ -15,7 +15,6 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
- // create a database and add a linked database view
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
@@ -30,11 +29,6 @@ void main() {
await tester.tapHidePropertyButton();
tester.noFieldWithName('New field 1');
- // create another field, New field 1 to be hidden still
- await tester.tapNewPropertyButton();
- await tester.dismissFieldEditor();
- tester.noFieldWithName('New field 1');
-
// go back to inline database view, expect field to be shown
await tester.tapTabBarLinkedViewByViewName('Untitled');
tester.findFieldWithName('New field 1');
@@ -66,40 +60,5 @@ void main() {
await tester.tapDatabaseSortButton();
await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1");
});
-
- testWidgets('field cell width', (tester) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
-
- // create a database and add a linked database view
- await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
- await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid);
-
- // create a field
- await tester.scrollToRight(find.byType(GridPage));
- await tester.tapNewPropertyButton();
- await tester.renameField('New field 1');
- await tester.dismissFieldEditor();
-
- // check the width of the field
- expect(tester.getFieldWidth('New field 1'), 150);
-
- // change the width of the field
- await tester.changeFieldWidth('New field 1', 200);
- expect(tester.getFieldWidth('New field 1'), 205);
-
- // create another field, New field 1 to be same width
- await tester.tapNewPropertyButton();
- await tester.dismissFieldEditor();
- expect(tester.getFieldWidth('New field 1'), 205);
-
- // go back to inline database view, expect New field 1 to be 150px
- await tester.tapTabBarLinkedViewByViewName('Untitled');
- expect(tester.getFieldWidth('New field 1'), 150);
-
- // go back to linked database view, expect New field 1 to be 205px
- await tester.tapTabBarLinkedViewByViewName('Grid');
- expect(tester.getFieldWidth('New field 1'), 205);
- });
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart
index 71656c1ea6..e35c9cc9d8 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart
@@ -1,10 +1,5 @@
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -78,37 +73,5 @@ void main() {
await tester.pumpAndSettle();
});
-
- testWidgets('insert grid in column', (tester) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
-
- /// create page and show slash menu
- await tester.createNewPageWithNameUnderParent(name: 'test page');
- await tester.editor.tapLineOfEditorAt(0);
- await tester.editor.showSlashMenu();
- await tester.pumpAndSettle();
-
- /// create a column
- await tester.editor.tapSlashMenuItemWithName(
- LocaleKeys.document_slashMenu_name_twoColumns.tr(),
- );
- final actionList = find.byType(BlockActionList);
- expect(actionList, findsNWidgets(2));
- final position = tester.getCenter(actionList.last);
-
- /// tap the second child of column
- await tester.tapAt(position.copyWith(dx: position.dx + 50));
-
- /// create a grid
- await tester.editor.showSlashMenu();
- await tester.pumpAndSettle();
- await tester.editor.tapSlashMenuItemWithName(
- LocaleKeys.document_slashMenu_name_grid.tr(),
- );
-
- final grid = find.byType(GridPageContent);
- expect(grid, findsOneWidget);
- });
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
index 1a8a3fcda8..ea8db6fdad 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart
@@ -27,9 +27,8 @@ void main() {
await tester.pumpAndSettle();
// click the align center
- await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
- await tester
- .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_center_m);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
// expect to see the align center
final editorState = tester.editor.getCurrentEditorState();
@@ -37,15 +36,13 @@ void main() {
expect(first.attributes[blockComponentAlign], 'center');
// click the align right
- await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
- await tester
- .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_right_m);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
expect(first.attributes[blockComponentAlign], 'right');
// click the align left
- await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m);
- await tester
- .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_left_m);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s);
+ await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s);
expect(first.attributes[blockComponentAlign], 'left');
});
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart
index d1e34edcb5..c18b42939c 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart
@@ -1,12 +1,10 @@
-import 'dart:async';
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
@@ -322,14 +320,8 @@ void main() {
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
- final pasteAsMenu = find.byType(PasteAsMenu);
- expect(pasteAsMenu, findsOneWidget);
- final bookmarkButton = find.text(
- LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(),
- );
- await tester.tapButton(bookmarkButton);
// the second one is the paragraph node
- expect(editorState.document.root.children.length, 1);
+ expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
@@ -341,20 +333,19 @@ void main() {
await tester.hoverOnWidget(
find.byType(CustomLinkPreviewWidget),
onHover: () async {
- /// show menu
- final menu = find.byType(CustomLinkPreviewMenu);
- expect(menu, findsOneWidget);
- await tester.tapButton(menu);
-
- final convertToLinkButton = find.text(
- LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl
- .tr(),
- );
+ final convertToLinkButton = find.byWidgetPredicate((widget) {
+ return widget is MenuBlockButton &&
+ widget.tooltip ==
+ LocaleKeys.document_plugins_urlPreview_convertToLink.tr();
+ });
expect(convertToLinkButton, findsOneWidget);
- await tester.tapButton(convertToLinkButton);
+ await tester.tap(convertToLinkButton);
+ await tester.pumpAndSettle();
},
);
+ await tester.pumpAndSettle();
+
final editorState = tester.editor.getCurrentEditorState();
final textNode = editorState.getNodeAtPath([0])!;
expect(textNode.type, ParagraphBlockKeys.type);
@@ -372,19 +363,14 @@ void main() {
(tester) async {
const url = 'https://appflowy.io';
await tester.pasteContent(plainText: url, (editorState) async {
- final pasteAsMenu = find.byType(PasteAsMenu);
- expect(pasteAsMenu, findsOneWidget);
- final bookmarkButton = find.text(
- LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(),
- );
- await tester.tapButton(bookmarkButton);
// the second one is the paragraph node
- expect(editorState.document.root.children.length, 1);
+ expect(editorState.document.root.children.length, 2);
final node = editorState.getNodeAtPath([0])!;
expect(node.type, LinkPreviewBlockKeys.type);
expect(node.attributes[LinkPreviewBlockKeys.url], url);
});
+ await tester.editor.tapLineOfEditorAt(0);
await tester.simulateKeyEvent(
LogicalKeyboardKey.keyZ,
isControlPressed:
@@ -483,6 +469,16 @@ void main() {
});
});
+ testWidgets('paste image url without extension', (tester) async {
+ const plainText =
+ 'https://images.unsplash.com/photo-1469474968028-56623f02e42e?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=david-marcu-78A265wPiO4-unsplash.jpg&w=640';
+ await tester.pasteContent(plainText: plainText, (editorState) {
+ final node = editorState.getNodeAtPath([0])!;
+ expect(node.type, ImageBlockKeys.type);
+ expect(node.attributes[ImageBlockKeys.url], isNotEmpty);
+ });
+ });
+
const testMarkdownText = '''
# I'm h1
## I'm h2
@@ -525,7 +521,7 @@ void main() {
extension on WidgetTester {
Future pasteContent(
- FutureOr Function(EditorState editorState) test, {
+ void Function(EditorState editorState) test, {
Future Function(EditorState editorState)? beforeTest,
String? plainText,
String? html,
@@ -562,6 +558,6 @@ extension on WidgetTester {
);
await pumpAndSettle(const Duration(milliseconds: 1000));
- await test(editor.getCurrentEditorState());
+ test(editor.getCurrentEditorState());
}
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart
index c2e00a4b48..43320509ce 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart
@@ -13,8 +13,6 @@ void main() {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
- final finder = find.text(gettingStarted, findRichText: true);
- await tester.pumpUntilFound(finder, timeout: const Duration(seconds: 2));
// create a new document
const pageName = 'Test Document';
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart
deleted file mode 100644
index 39f8bfd4f6..0000000000
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart
+++ /dev/null
@@ -1,453 +0,0 @@
-import 'dart:io';
-
-import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart';
-import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_test/flutter_test.dart';
-import 'package:integration_test/integration_test.dart';
-
-import '../../shared/util.dart';
-
-void main() {
- IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
- const avaliableLink = 'https://appflowy.io/',
- unavailableLink = 'www.thereIsNoting.com';
-
- Future preparePage(WidgetTester tester, {String? pageName}) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
- await tester.createNewPageWithNameUnderParent(name: pageName);
- await tester.editor.tapLineOfEditorAt(0);
- }
-
- Future pasteLink(WidgetTester tester, String link) async {
- await getIt()
- .setData(ClipboardServiceData(plainText: link));
-
- /// paste the link
- await tester.simulateKeyEvent(
- LogicalKeyboardKey.keyV,
- isControlPressed: Platform.isLinux || Platform.isWindows,
- isMetaPressed: Platform.isMacOS,
- );
- await tester.pumpAndSettle(Duration(seconds: 1));
- }
-
- Future pasteAs(
- WidgetTester tester,
- String link,
- PasteMenuType type, {
- Duration waitTime = const Duration(milliseconds: 500),
- }) async {
- await pasteLink(tester, link);
- final convertToMentionButton = find.text(type.title);
- await tester.tapButton(convertToMentionButton);
- await tester.pumpAndSettle(waitTime);
- }
-
- void checkUrl(Node node, String link) {
- expect(node.type, ParagraphBlockKeys.type);
- expect(node.delta!.toJson(), [
- {
- 'insert': link,
- 'attributes': {'href': link},
- }
- ]);
- }
-
- void checkMention(Node node, String link) {
- final delta = node.delta!;
- final insert = (delta.first as TextInsert).text;
- final attributes = delta.first.attributes;
- expect(insert, MentionBlockKeys.mentionChar);
- final mention =
- attributes?[MentionBlockKeys.mention] as Map;
- expect(mention[MentionBlockKeys.type], MentionType.externalLink.name);
- expect(mention[MentionBlockKeys.url], avaliableLink);
- }
-
- void checkBookmark(Node node, String link) {
- expect(node.type, LinkPreviewBlockKeys.type);
- expect(node.attributes[LinkPreviewBlockKeys.url], link);
- }
-
- void checkEmbed(Node node, String link) {
- expect(node.type, LinkPreviewBlockKeys.type);
- expect(node.attributes[LinkEmbedKeys.previewType], LinkEmbedKeys.embed);
- expect(node.attributes[LinkPreviewBlockKeys.url], link);
- }
-
- group('Paste as URL', () {
- Future pasteAndTurnInto(
- WidgetTester tester,
- String link,
- String title,
- ) async {
- await pasteLink(tester, link);
- final convertToLinkButton = find
- .text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr());
- await tester.tapButton(convertToLinkButton);
-
- /// hover link and turn into mention
- await tester.hoverOnWidget(
- find.byType(LinkHoverTrigger),
- onHover: () async {
- final turnintoButton = find.byFlowySvg(FlowySvgs.turninto_m);
- await tester.tapButton(turnintoButton);
- final convertToButton = find.text(title);
- await tester.tapButton(convertToButton);
- await tester.pumpAndSettle(Duration(seconds: 1));
- },
- );
- }
-
- testWidgets('paste a link', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteLink(tester, link);
- final convertToLinkButton = find
- .text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr());
- await tester.tapButton(convertToLinkButton);
- final node = tester.editor.getNodeAtPath([0]);
- checkUrl(node, link);
- });
-
- testWidgets('paste a link and turn into mention', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAndTurnInto(
- tester,
- link,
- LinkConvertMenuCommand.toMention.title,
- );
-
- /// check metion values
- final node = tester.editor.getNodeAtPath([0]);
- checkMention(node, link);
- });
-
- testWidgets('paste a link and turn into bookmark', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAndTurnInto(
- tester,
- link,
- LinkConvertMenuCommand.toBookmark.title,
- );
-
- /// check metion values
- final node = tester.editor.getNodeAtPath([0]);
- checkBookmark(node, link);
- });
-
- testWidgets('paste a link and turn into embed', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAndTurnInto(
- tester,
- link,
- LinkConvertMenuCommand.toEmbed.title,
- );
-
- /// check metion values
- final node = tester.editor.getNodeAtPath([0]);
- checkEmbed(node, link);
- });
- });
-
- group('Paste as Mention', () {
- Future pasteAsMention(WidgetTester tester, String link) =>
- pasteAs(tester, link, PasteMenuType.mention);
-
- String getMentionLink(Node node) {
- final insert = node.delta?.first as TextInsert?;
- final mention = insert?.attributes?[MentionBlockKeys.mention]
- as Map?;
- return mention?[MentionBlockKeys.url] ?? '';
- }
-
- Future hoverMentionAndClick(
- WidgetTester tester,
- String command,
- ) async {
- final mentionLink = find.byType(MentionLinkBlock);
- expect(mentionLink, findsOneWidget);
- await tester.hoverOnWidget(
- mentionLink,
- onHover: () async {
- final errorPreview = find.byType(MentionLinkErrorPreview);
- expect(errorPreview, findsOneWidget);
- final convertButton = find.byFlowySvg(FlowySvgs.turninto_m);
- await tester.tapButton(convertButton);
- final menuButton = find.text(command);
- await tester.tapButton(menuButton);
- },
- );
- }
-
- testWidgets('paste a link as mention', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- final node = tester.editor.getNodeAtPath([0]);
- checkMention(node, link);
- });
-
- testWidgets('paste as mention and copy link', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- final mentionLink = find.byType(MentionLinkBlock);
- expect(mentionLink, findsOneWidget);
- await tester.hoverOnWidget(
- mentionLink,
- onHover: () async {
- final preview = find.byType(MentionLinkPreview);
- if (!preview.hasFound) {
- final copyButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
- await tester.tapButton(copyButton);
- } else {
- final moreOptionButton = find.byFlowySvg(FlowySvgs.toolbar_more_m);
- await tester.tapButton(moreOptionButton);
- final copyButton =
- find.text(MentionLinktMenuCommand.copyLink.title);
- await tester.tapButton(copyButton);
- }
- },
- );
- final clipboardContent = await getIt().getData();
- expect(clipboardContent.plainText, link);
- });
-
- testWidgets('paste as error mention and turninto url', (tester) async {
- String link = unavailableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- Node node = tester.editor.getNodeAtPath([0]);
- link = getMentionLink(node);
- await hoverMentionAndClick(
- tester,
- MentionLinktErrorMenuCommand.toURL.title,
- );
- node = tester.editor.getNodeAtPath([0]);
- checkUrl(node, link);
- });
-
- testWidgets('paste as error mention and turninto embed', (tester) async {
- String link = unavailableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- Node node = tester.editor.getNodeAtPath([0]);
- link = getMentionLink(node);
- await hoverMentionAndClick(
- tester,
- MentionLinktErrorMenuCommand.toEmbed.title,
- );
- node = tester.editor.getNodeAtPath([0]);
- checkEmbed(node, link);
- });
-
- testWidgets('paste as error mention and turninto bookmark', (tester) async {
- String link = unavailableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- Node node = tester.editor.getNodeAtPath([0]);
- link = getMentionLink(node);
- await hoverMentionAndClick(
- tester,
- MentionLinktErrorMenuCommand.toBookmark.title,
- );
- node = tester.editor.getNodeAtPath([0]);
- checkBookmark(node, link);
- });
-
- testWidgets('paste as error mention and remove link', (tester) async {
- String link = unavailableLink;
- await preparePage(tester);
- await pasteAsMention(tester, link);
- Node node = tester.editor.getNodeAtPath([0]);
- link = getMentionLink(node);
- await hoverMentionAndClick(
- tester,
- MentionLinktErrorMenuCommand.removeLink.title,
- );
- node = tester.editor.getNodeAtPath([0]);
- expect(node.type, ParagraphBlockKeys.type);
- expect(node.delta!.toJson(), [
- {'insert': link},
- ]);
- });
- });
-
- group('Paste as Bookmark', () {
- Future pasteAsBookmark(WidgetTester tester, String link) =>
- pasteAs(tester, link, PasteMenuType.bookmark);
-
- Future hoverAndClick(
- WidgetTester tester,
- LinkPreviewMenuCommand command,
- ) async {
- final bookmark = find.byType(CustomLinkPreviewBlockComponent);
- expect(bookmark, findsOneWidget);
- await tester.hoverOnWidget(
- bookmark,
- onHover: () async {
- final menuButton = find.byFlowySvg(FlowySvgs.toolbar_more_m);
- await tester.tapButton(menuButton);
- final commandButton = find.text(command.title);
- await tester.tapButton(commandButton);
- },
- );
- }
-
- testWidgets('paste a link as bookmark', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- final node = tester.editor.getNodeAtPath([0]);
- checkBookmark(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to mention',
- (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.convertToMention);
- final node = tester.editor.getNodeAtPath([0]);
- checkMention(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to url', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.convertToUrl);
- final node = tester.editor.getNodeAtPath([0]);
- checkUrl(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to embed',
- (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.convertToEmbed);
- final node = tester.editor.getNodeAtPath([0]);
- checkEmbed(node, link);
- });
-
- testWidgets('paste a link as bookmark and copy link', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.copyLink);
- final clipboardContent = await getIt().getData();
- expect(clipboardContent.plainText, link);
- });
-
- testWidgets('paste a link as bookmark and replace link', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.replace);
- await tester.simulateKeyEvent(
- LogicalKeyboardKey.keyA,
- isControlPressed: Platform.isLinux || Platform.isWindows,
- isMetaPressed: Platform.isMacOS,
- );
- await tester.simulateKeyEvent(LogicalKeyboardKey.delete);
- await tester.enterText(find.byType(TextFormField), unavailableLink);
- await tester.tapButton(find.text(LocaleKeys.button_replace.tr()));
- final node = tester.editor.getNodeAtPath([0]);
- checkBookmark(node, unavailableLink);
- });
-
- testWidgets('paste a link as bookmark and remove link', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsBookmark(tester, link);
- await hoverAndClick(tester, LinkPreviewMenuCommand.removeLink);
- final node = tester.editor.getNodeAtPath([0]);
- expect(node.type, ParagraphBlockKeys.type);
- expect(node.delta!.toJson(), [
- {'insert': link},
- ]);
- });
- });
- group('Paste as Embed', () {
- Future pasteAsEmbed(WidgetTester tester, String link) =>
- pasteAs(tester, link, PasteMenuType.embed);
-
- Future hoverAndConvert(
- WidgetTester tester,
- LinkEmbedConvertCommand command,
- ) async {
- final embed = find.byType(LinkEmbedBlockComponent);
- expect(embed, findsOneWidget);
- await tester.hoverOnWidget(
- embed,
- onHover: () async {
- final menuButton = find.byFlowySvg(FlowySvgs.turninto_m);
- await tester.tapButton(menuButton);
- final commandButton = find.text(command.title);
- await tester.tapButton(commandButton);
- },
- );
- }
-
- testWidgets('paste a link as embed', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsEmbed(tester, link);
- final node = tester.editor.getNodeAtPath([0]);
- checkEmbed(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to mention',
- (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsEmbed(tester, link);
- await hoverAndConvert(tester, LinkEmbedConvertCommand.toMention);
- final node = tester.editor.getNodeAtPath([0]);
- checkMention(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to url', (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsEmbed(tester, link);
- await hoverAndConvert(tester, LinkEmbedConvertCommand.toURL);
- final node = tester.editor.getNodeAtPath([0]);
- checkUrl(node, link);
- });
-
- testWidgets('paste a link as bookmark and convert to bookmark',
- (tester) async {
- final link = avaliableLink;
- await preparePage(tester);
- await pasteAsEmbed(tester, link);
- await hoverAndConvert(tester, LinkEmbedConvertCommand.toBookmark);
- final node = tester.editor.getNodeAtPath([0]);
- checkBookmark(node, link);
- });
- });
-}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart
index 6ec12287a8..cbc634cf02 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart
@@ -76,12 +76,13 @@ void main() {
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
- LocaleKeys.editor_bulletedListShortForm.tr():
+ LocaleKeys.document_slashMenu_name_bulletedList.tr():
BulletedListBlockKeys.type,
- LocaleKeys.editor_numberedListShortForm.tr():
+ LocaleKeys.document_slashMenu_name_numberedList.tr():
NumberedListBlockKeys.type,
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
- LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type,
+ LocaleKeys.document_slashMenu_name_todoList.tr():
+ TodoListBlockKeys.type,
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
};
@@ -116,12 +117,13 @@ void main() {
LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type,
LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type,
- LocaleKeys.editor_bulletedListShortForm.tr():
+ LocaleKeys.document_slashMenu_name_bulletedList.tr():
BulletedListBlockKeys.type,
- LocaleKeys.editor_numberedListShortForm.tr():
+ LocaleKeys.document_slashMenu_name_numberedList.tr():
NumberedListBlockKeys.type,
LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type,
- LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type,
+ LocaleKeys.document_slashMenu_name_todoList.tr():
+ TodoListBlockKeys.type,
LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type,
LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type,
};
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart
index de1cb880a5..bd0fd18c50 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart
@@ -1,6 +1,5 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -48,41 +47,5 @@ void main() {
expect(editorState.selection!.start.offset, 0);
});
-
- testWidgets('select and delete text', (tester) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
-
- /// create a new document
- await tester.createNewPageWithNameUnderParent();
-
- /// input text
- final editor = tester.editor;
- final editorState = editor.getCurrentEditorState();
-
- const inputText = 'Test for text selection and deletion';
- final texts = inputText.split(' ');
- await editor.tapLineOfEditorAt(0);
- await tester.ime.insertText(inputText);
-
- /// selecte and delete
- int index = 0;
- while (texts.isNotEmpty) {
- final text = texts.removeAt(0);
- await tester.editor.updateSelection(
- Selection(
- start: Position(path: [0], offset: index),
- end: Position(path: [0], offset: index + text.length),
- ),
- );
- await tester.simulateKeyEvent(LogicalKeyboardKey.delete);
- index++;
- }
-
- /// excpete the text value is correct
- final node = editorState.getNodeAtPath([0])!;
- final nodeText = node.delta?.toPlainText() ?? '';
- expect(nodeText, ' ' * (index - 1));
- });
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart
index bc0671834b..a05545753e 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart
@@ -13,7 +13,6 @@ import 'document_with_multi_image_block_test.dart'
as document_with_multi_image_block_test;
import 'document_with_simple_table_test.dart'
as document_with_simple_table_test;
-import 'document_link_preview_test.dart' as document_link_preview_test;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
@@ -29,5 +28,4 @@ void main() {
document_find_menu_test.main();
document_toolbar_test.main();
document_with_simple_table_test.main();
- document_link_preview_test.main();
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart
index f455cd479d..c3a086626f 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart
@@ -1,19 +1,5 @@
-import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart';
-import 'package:appflowy/startup/startup.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -22,33 +8,24 @@ import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
- Future selectText(WidgetTester tester, String text) async {
- await tester.editor.updateSelection(
- Selection.single(
- path: [0],
- startOffset: 0,
- endOffset: text.length,
- ),
- );
- }
-
- Future prepareForToolbar(WidgetTester tester, String text) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
-
- await tester.createNewPageWithNameUnderParent();
-
- await tester.editor.tapLineOfEditorAt(0);
- await tester.ime.insertText(text);
- await selectText(tester, text);
- }
-
group('document toolbar:', () {
testWidgets('font family', (tester) async {
- await prepareForToolbar(tester, 'font family');
+ await tester.initializeAppFlowy();
+ await tester.tapAnonymousSignInButton();
+
+ await tester.createNewPageWithNameUnderParent();
+
+ await tester.editor.tapLineOfEditorAt(0);
+ const text = 'font family';
+ await tester.ime.insertText(text);
+ await tester.editor.updateSelection(
+ Selection.single(
+ path: [0],
+ startOffset: 0,
+ endOffset: text.length,
+ ),
+ );
- // tap more options button
- await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_more_m);
// tap the font family button
final fontFamilyButton = find.byKey(kFontFamilyToolbarItemKey);
await tester.tapButton(fontFamilyButton);
@@ -69,302 +46,5 @@ void main() {
abel,
);
});
-
- testWidgets('heading 1~3', (tester) async {
- const text = 'heading';
- await prepareForToolbar(tester, text);
-
- Future testChangeHeading(
- FlowySvgData svg,
- String title,
- int level,
- ) async {
- /// tap suggestions item
- final suggestionsButton = find.byKey(kSuggestionsItemKey);
- await tester.tapButton(suggestionsButton);
-
- /// tap item
- await tester.ensureVisible(find.byFlowySvg(svg));
- await tester.tapButton(find.byFlowySvg(svg));
-
- /// check the type of node is [HeadingBlockKeys.type]
- await selectText(tester, text);
- final editorState = tester.editor.getCurrentEditorState();
- final selection = editorState.selection!;
- final node = editorState.getNodeAtPath(selection.start.path)!,
- nodeLevel = node.attributes[HeadingBlockKeys.level]!;
- expect(node.type, HeadingBlockKeys.type);
- expect(nodeLevel, level);
-
- /// show toolbar again
- await selectText(tester, text);
-
- /// the text of suggestions item should be changed
- expect(
- find.descendant(of: suggestionsButton, matching: find.text(title)),
- findsOneWidget,
- );
- }
-
- await testChangeHeading(
- FlowySvgs.type_h1_m,
- LocaleKeys.document_toolbar_h1.tr(),
- 1,
- );
-
- await testChangeHeading(
- FlowySvgs.type_h2_m,
- LocaleKeys.document_toolbar_h2.tr(),
- 2,
- );
- await testChangeHeading(
- FlowySvgs.type_h3_m,
- LocaleKeys.document_toolbar_h3.tr(),
- 3,
- );
- });
-
- testWidgets('toggle 1~3', (tester) async {
- const text = 'toggle';
- await prepareForToolbar(tester, text);
-
- Future testChangeToggle(
- FlowySvgData svg,
- String title,
- int? level,
- ) async {
- /// tap suggestions item
- final suggestionsButton = find.byKey(kSuggestionsItemKey);
- await tester.tapButton(suggestionsButton);
-
- /// tap item
- await tester.ensureVisible(find.byFlowySvg(svg));
- await tester.tapButton(find.byFlowySvg(svg));
-
- /// check the type of node is [HeadingBlockKeys.type]
- await selectText(tester, text);
- final editorState = tester.editor.getCurrentEditorState();
- final selection = editorState.selection!;
- final node = editorState.getNodeAtPath(selection.start.path)!,
- nodeLevel = node.attributes[ToggleListBlockKeys.level];
- expect(node.type, ToggleListBlockKeys.type);
- expect(nodeLevel, level);
-
- /// show toolbar again
- await selectText(tester, text);
-
- /// the text of suggestions item should be changed
- expect(
- find.descendant(of: suggestionsButton, matching: find.text(title)),
- findsOneWidget,
- );
- }
-
- await testChangeToggle(
- FlowySvgs.type_toggle_list_m,
- LocaleKeys.editor_toggleListShortForm.tr(),
- null,
- );
-
- await testChangeToggle(
- FlowySvgs.type_toggle_h1_m,
- LocaleKeys.editor_toggleHeading1ShortForm.tr(),
- 1,
- );
-
- await testChangeToggle(
- FlowySvgs.type_toggle_h2_m,
- LocaleKeys.editor_toggleHeading2ShortForm.tr(),
- 2,
- );
-
- await testChangeToggle(
- FlowySvgs.type_toggle_h3_m,
- LocaleKeys.editor_toggleHeading3ShortForm.tr(),
- 3,
- );
- });
-
- testWidgets('toolbar will not rebuild after click item', (tester) async {
- const text = 'Test rebuilding';
- await prepareForToolbar(tester, text);
- Finder toolbar = find.byType(DesktopFloatingToolbar);
- Element toolbarElement = toolbar.evaluate().first;
- final elementHashcode = toolbarElement.hashCode;
- final boldButton = find.byFlowySvg(FlowySvgs.toolbar_bold_m),
- underlineButton = find.byFlowySvg(FlowySvgs.toolbar_underline_m),
- italicButton = find.byFlowySvg(FlowySvgs.toolbar_inline_italic_m);
-
- /// tap format buttons
- await tester.tapButton(boldButton);
- await tester.tapButton(underlineButton);
- await tester.tapButton(italicButton);
- toolbar = find.byType(DesktopFloatingToolbar);
- toolbarElement = toolbar.evaluate().first;
-
- /// check if the toolbar is not rebuilt
- expect(elementHashcode, toolbarElement.hashCode);
- final editorState = tester.editor.getCurrentEditorState();
-
- /// check text formats
- expect(
- editorState
- .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.bold),
- true,
- );
- expect(
- editorState
- .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.italic),
- true,
- );
- expect(
- editorState
- .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.underline),
- true,
- );
- });
- });
-
- group('document toolbar: link', () {
- String? getLinkFromNode(Node node) {
- for (final insert in node.delta!) {
- final link = insert.attributes?.href;
- if (link != null) return link;
- }
- return null;
- }
-
- bool isPageLink(Node node) {
- for (final insert in node.delta!) {
- final isPage = insert.attributes?.isPage;
- if (isPage == true) return true;
- }
- return false;
- }
-
- String getNodeText(Node node) {
- for (final insert in node.delta!) {
- if (insert is TextInsert) return insert.text;
- }
- return '';
- }
-
- testWidgets('insert link and remove link', (tester) async {
- const text = 'insert link', link = 'https://test.appflowy.cloud';
- await prepareForToolbar(tester, text);
-
- final toolbar = find.byType(DesktopFloatingToolbar);
- expect(toolbar, findsOneWidget);
-
- /// tap link button to show CreateLinkMenu
- final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
- await tester.tapButton(linkButton);
- final createLinkMenu = find.byType(LinkCreateMenu);
- expect(createLinkMenu, findsOneWidget);
-
- /// test esc to close
- await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
- expect(toolbar, findsNothing);
-
- /// show toolbar again
- await tester.editor.tapLineOfEditorAt(0);
- await selectText(tester, text);
- await tester.tapButton(linkButton);
-
- /// insert link
- final textField = find.descendant(
- of: createLinkMenu,
- matching: find.byType(TextFormField),
- );
- await tester.enterText(textField, link);
- await tester.pumpAndSettle();
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
- Node node = tester.editor.getNodeAtPath([0]);
- expect(getLinkFromNode(node), link);
- await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
-
- /// hover link
- await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
- final hoverMenu = find.byType(LinkHoverMenu);
- expect(hoverMenu, findsOneWidget);
-
- /// copy link
- final copyButton = find.descendant(
- of: hoverMenu,
- matching: find.byFlowySvg(FlowySvgs.toolbar_link_m),
- );
- await tester.tapButton(copyButton);
- final clipboardContent = await getIt().getData();
- final plainText = clipboardContent.plainText;
- expect(plainText, link);
-
- /// remove link
- await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
- await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_link_unlink_m));
- node = tester.editor.getNodeAtPath([0]);
- expect(getLinkFromNode(node), null);
- });
-
- testWidgets('insert link and edit link', (tester) async {
- const text = 'edit link',
- link = 'https://test.appflowy.cloud',
- afterText = '$text after';
- await prepareForToolbar(tester, text);
-
- /// tap link button to show CreateLinkMenu
- final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m);
- await tester.tapButton(linkButton);
-
- /// search for page and select it
- final textField = find.descendant(
- of: find.byType(LinkCreateMenu),
- matching: find.byType(TextFormField),
- );
- await tester.enterText(textField, gettingStarted);
- await tester.pumpAndSettle();
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
- await tester.simulateKeyEvent(LogicalKeyboardKey.escape);
-
- Node node = tester.editor.getNodeAtPath([0]);
- expect(isPageLink(node), true);
- expect(getLinkFromNode(node) == link, false);
-
- /// hover link
- await tester.hoverOnWidget(find.byType(LinkHoverTrigger));
-
- /// click edit button to show LinkEditMenu
- final editButton = find.byFlowySvg(FlowySvgs.toolbar_link_edit_m);
- await tester.tapButton(editButton);
- final linkEditMenu = find.byType(LinkEditMenu);
- expect(linkEditMenu, findsOneWidget);
-
- /// change the link text
- final titleField = find.descendant(
- of: linkEditMenu,
- matching: find.byType(TextFormField),
- );
- await tester.enterText(titleField, afterText);
- await tester.pumpAndSettle();
- await tester.tapButton(
- find.descendant(of: linkEditMenu, matching: find.text(gettingStarted)),
- );
- final linkField = find.ancestor(
- of: find.text(LocaleKeys.document_toolbar_linkInputHint.tr()),
- matching: find.byType(TextFormField),
- );
- await tester.enterText(linkField, link);
- await tester.pumpAndSettle();
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
-
- /// apply the change
- final applyButton =
- find.text(LocaleKeys.settings_appearance_documentSettings_apply.tr());
- await tester.tapButton(applyButton);
-
- node = tester.editor.getNodeAtPath([0]);
- expect(isPageLink(node), false);
- expect(getLinkFromNode(node), link);
- expect(getNodeText(node), afterText);
- });
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart
index 67e0149cd1..906b5ab69c 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart
@@ -1,4 +1,3 @@
-import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -34,15 +33,9 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
- // tap the more options button
- final moreOptionButton = find.findFlowyTooltip(
- LocaleKeys.document_toolbar_moreOptions.tr(),
- );
- await tester.tapButton(moreOptionButton);
-
// tap the inline math equation button
- final inlineMathEquationButton = find.text(
- LocaleKeys.document_toolbar_equation.tr(),
+ final inlineMathEquationButton = find.findFlowyTooltip(
+ LocaleKeys.document_plugins_createInlineMathEquation.tr(),
);
await tester.tapButton(inlineMathEquationButton);
@@ -85,15 +78,10 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
- // tap the more options button
- final moreOptionButton = find.findFlowyTooltip(
- LocaleKeys.document_toolbar_moreOptions.tr(),
- );
- await tester.tapButton(moreOptionButton);
-
// tap the inline math equation button
- final inlineMathEquationButton =
- find.byFlowySvg(FlowySvgs.type_formula_m);
+ var inlineMathEquationButton = find.findFlowyTooltip(
+ LocaleKeys.document_plugins_createInlineMathEquation.tr(),
+ );
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
@@ -105,7 +93,17 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: 1),
);
- await tester.tapButton(moreOptionButton);
+ // expect to the see the inline math equation button is highlighted
+ inlineMathEquationButton = find.descendant(
+ of: find.findFlowyTooltip(
+ LocaleKeys.document_plugins_createInlineMathEquation.tr(),
+ ),
+ matching: find.byType(SVGIconItemWidget),
+ );
+ expect(
+ tester.widget(inlineMathEquationButton).isHighlight,
+ isTrue,
+ );
// cancel the format
await tester.tapButton(inlineMathEquationButton);
@@ -136,15 +134,10 @@ void main() {
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
);
- // tap the more options button
- final moreOptionButton = find.findFlowyTooltip(
- LocaleKeys.document_toolbar_moreOptions.tr(),
- );
- await tester.tapButton(moreOptionButton);
-
// tap the inline math equation button
- final inlineMathEquationButton =
- find.byFlowySvg(FlowySvgs.type_formula_m);
+ final inlineMathEquationButton = find.findFlowyTooltip(
+ LocaleKeys.document_plugins_createInlineMathEquation.tr(),
+ );
await tester.tapButton(inlineMathEquationButton);
// expect to see the math equation block
diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart
index c4aa289855..8eb47fc15f 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart
@@ -1,4 +1,3 @@
-import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -86,10 +85,16 @@ void main() {
),
);
- await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_text_format_m));
+ await tester.tapButton(find.byType(HeadingPopup));
+ await tester.pumpAndSettle();
+
+ expect(
+ find.byType(HeadingButton),
+ findsNWidgets(3),
+ );
// tap the H1 button
- await tester.tapButton(find.byFlowySvg(FlowySvgs.type_h1_m).at(0));
+ await tester.tapButton(find.byType(HeadingButton).at(0));
await tester.pumpAndSettle();
final editorState = tester.editor.getCurrentEditorState();
diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart
index d3226a3ad0..554a6eecbf 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart
@@ -1,166 +1,42 @@
import 'dart:io';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/emoji/emoji_handler.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
+import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
+import 'package:appflowy_editor/src/editor/editor_component/service/editor.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
+
+import '../../shared/keyboard.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
- Future prepare(WidgetTester tester) async {
- await tester.initializeAppFlowy();
- await tester.tapAnonymousSignInButton();
- await tester.createNewPageWithNameUnderParent();
- await tester.editor.tapLineOfEditorAt(0);
- }
-
// May be better to move this to an existing test but unsure what it fits with
group('Keyboard shortcuts related to emojis', () {
testWidgets('cmd/ctrl+alt+e shortcut opens the emoji picker',
(tester) async {
- await prepare(tester);
+ await tester.initializeAppFlowy();
+ await tester.tapAnonymousSignInButton();
- expect(find.byType(EmojiHandler), findsNothing);
+ final Finder editor = find.byType(AppFlowyEditor);
+ await tester.tap(editor);
+ await tester.pumpAndSettle();
- await tester.simulateKeyEvent(
- LogicalKeyboardKey.keyE,
- isAltPressed: true,
- isMetaPressed: Platform.isMacOS,
- isControlPressed: !Platform.isMacOS,
+ expect(find.byType(EmojiSelectionMenu), findsNothing);
+
+ await FlowyTestKeyboard.simulateKeyDownEvent(
+ [
+ Platform.isMacOS
+ ? LogicalKeyboardKey.meta
+ : LogicalKeyboardKey.control,
+ LogicalKeyboardKey.alt,
+ LogicalKeyboardKey.keyE,
+ ],
+ tester: tester,
);
- await tester.pumpAndSettle(Duration(seconds: 1));
- expect(find.byType(EmojiHandler), findsOneWidget);
- /// press backspace to hide the emoji picker
- await tester.simulateKeyEvent(LogicalKeyboardKey.backspace);
- expect(find.byType(EmojiHandler), findsNothing);
- });
-
- testWidgets('insert emoji by slash menu', (tester) async {
- await prepare(tester);
- await tester.editor.showSlashMenu();
-
- /// show emoji picler
- await tester.editor.tapSlashMenuItemWithName(
- LocaleKeys.document_slashMenu_name_emoji.tr(),
- offset: 100,
- );
- await tester.pumpAndSettle(Duration(seconds: 1));
- expect(find.byType(EmojiHandler), findsOneWidget);
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
- final firstNode =
- tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
-
- /// except the emoji is in document
- expect(firstNode.delta!.toPlainText().contains('😀'), true);
- });
- });
-
- group('insert emoji by colon', () {
- Future createNewDocumentAndShowEmojiList(
- WidgetTester tester, {
- String? search,
- }) async {
- await prepare(tester);
- await tester.ime.insertText(':${search ?? 'a'}');
- await tester.pumpAndSettle(Duration(seconds: 1));
- }
-
- testWidgets('insert with click', (tester) async {
- await createNewDocumentAndShowEmojiList(tester);
-
- /// emoji list is showing
- final emojiHandler = find.byType(EmojiHandler);
- expect(emojiHandler, findsOneWidget);
- final emojiButtons =
- find.descendant(of: emojiHandler, matching: find.byType(FlowyButton));
- final firstTextFinder = find.descendant(
- of: emojiButtons.first,
- matching: find.byType(FlowyText),
- );
- final emojiText =
- (firstTextFinder.evaluate().first.widget as FlowyText).text;
-
- /// click first emoji item
- await tester.tapButton(emojiButtons.first);
- final firstNode =
- tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
-
- /// except the emoji is in document
- expect(emojiText.contains(firstNode.delta!.toPlainText()), true);
- });
-
- testWidgets('insert with arrow and enter', (tester) async {
- await createNewDocumentAndShowEmojiList(tester);
-
- /// emoji list is showing
- final emojiHandler = find.byType(EmojiHandler);
- expect(emojiHandler, findsOneWidget);
- final emojiButtons =
- find.descendant(of: emojiHandler, matching: find.byType(FlowyButton));
-
- /// tap arrow down and arrow up
- await tester.simulateKeyEvent(LogicalKeyboardKey.arrowUp);
- await tester.simulateKeyEvent(LogicalKeyboardKey.arrowDown);
-
- final firstTextFinder = find.descendant(
- of: emojiButtons.first,
- matching: find.byType(FlowyText),
- );
- final emojiText =
- (firstTextFinder.evaluate().first.widget as FlowyText).text;
-
- /// tap enter
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
- final firstNode =
- tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
-
- /// except the emoji is in document
- expect(emojiText.contains(firstNode.delta!.toPlainText()), true);
- });
-
- testWidgets('insert with searching', (tester) async {
- await createNewDocumentAndShowEmojiList(tester, search: 's');
-
- /// search for `smiling eyes`, IME is not working, use keyboard input
- final searchText = [
- LogicalKeyboardKey.keyM,
- LogicalKeyboardKey.keyI,
- LogicalKeyboardKey.keyL,
- LogicalKeyboardKey.keyI,
- LogicalKeyboardKey.keyN,
- LogicalKeyboardKey.keyG,
- LogicalKeyboardKey.space,
- LogicalKeyboardKey.keyE,
- LogicalKeyboardKey.keyY,
- LogicalKeyboardKey.keyE,
- LogicalKeyboardKey.keyS,
- ];
-
- for (final key in searchText) {
- await tester.simulateKeyEvent(key);
- }
-
- /// tap enter
- await tester.simulateKeyEvent(LogicalKeyboardKey.enter);
- final firstNode =
- tester.editor.getCurrentEditorState().getNodeAtPath([0])!;
-
- /// except the emoji is in document
- expect(firstNode.delta!.toPlainText().contains('😄'), true);
- });
-
- testWidgets('start searching with sapce', (tester) async {
- await createNewDocumentAndShowEmojiList(tester, search: ' ');
-
- /// emoji list is showing
- final emojiHandler = find.byType(EmojiHandler);
- expect(emojiHandler, findsNothing);
+ expect(find.byType(EmojiSelectionMenu), findsOneWidget);
});
});
}
diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart
index 1d0f13eebc..4a38dde920 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart
@@ -1,12 +1,13 @@
import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@@ -37,7 +38,7 @@ void main() {
LocaleKeys.settings_workspacePage_appearance_options_light.tr(),
),
);
- await tester.pumpAndSettle(const Duration(milliseconds: 250));
+ await tester.pumpAndSettle();
themeMode = tester.widget(appFinder).themeMode;
expect(themeMode, ThemeMode.light);
@@ -47,7 +48,7 @@ void main() {
LocaleKeys.settings_workspacePage_appearance_options_dark.tr(),
),
);
- await tester.pumpAndSettle(const Duration(milliseconds: 250));
+ await tester.pumpAndSettle();
themeMode = tester.widget(appFinder).themeMode;
expect(themeMode, ThemeMode.dark);
@@ -65,11 +66,10 @@ void main() {
],
tester: tester,
);
- await tester.pumpAndSettle(const Duration(milliseconds: 500));
+ await tester.pumpAndSettle();
- // disable it temporarily. It works on macOS but not on Linux.
- // themeMode = tester.widget(appFinder).themeMode;
- // expect(themeMode, ThemeMode.light);
+ themeMode = tester.widget(appFinder).themeMode;
+ expect(themeMode, ThemeMode.light);
});
testWidgets('show or hide home menu', (tester) async {
diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart
index 836cfe4ccd..f7d94e8b4a 100644
--- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart
+++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart
@@ -13,6 +13,7 @@ void main() {
hotkeys_test.main();
emoji_shortcut_test.main();
hotkeys_test.main();
+ emoji_shortcut_test.main();
share_markdown_test.main();
import_files_test.main();
zoom_in_out_test.main();
diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
index d7a505d152..4a117a71ff 100644
--- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart
@@ -67,10 +67,12 @@ extension CommonOperations on WidgetTester {
} else {
// cloud version
final anonymousButton = find.byType(SignInAnonymousButtonV2);
- await tapButton(anonymousButton, warnIfMissed: true);
+ await tapButton(anonymousButton);
}
- await pumpAndSettle(const Duration(milliseconds: 200));
+ if (Platform.isWindows) {
+ await pumpAndSettle(const Duration(milliseconds: 200));
+ }
}
Future tapContinousAnotherWay() async {
diff --git a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart
index 970965f294..5f27305fe5 100644
--- a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart
@@ -1,9 +1,17 @@
import 'dart:io';
+import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
+import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart';
+import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
+import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
+import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart';
-import 'package:appflowy/plugins/database/application/field/filter_entities.dart';
import 'package:appflowy/plugins/database/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart';
import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart';
@@ -19,11 +27,10 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart';
-import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/date.dart';
-import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart';
+import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/date.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/disclosure_button.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart';
@@ -37,7 +44,6 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filt
import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart';
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dart';
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart';
-import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart';
@@ -70,8 +76,6 @@ import 'package:appflowy/plugins/database/widgets/setting/database_setting_actio
import 'package:appflowy/plugins/database/widgets/setting/database_settings_list.dart';
import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart';
import 'package:appflowy/plugins/database/widgets/setting/setting_property_list.dart';
-import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
-import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/clear_date_button.dart';
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
@@ -86,9 +90,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text_input.dart';
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
-import 'package:flutter/gestures.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart' as p;
// Non-exported member of the table_calendar library
@@ -942,31 +943,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
await pumpAndSettle(const Duration(milliseconds: 200));
}
- Future changeFieldWidth(String fieldName, double width) async {
- final field = find.byWidgetPredicate(
- (widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
- );
- await hoverOnWidget(
- field,
- onHover: () async {
- final dragHandle = find.descendant(
- of: field,
- matching: find.byType(DragToExpandLine),
- );
- await drag(dragHandle, Offset(width - getSize(field).width, 0));
- await pumpAndSettle(const Duration(milliseconds: 200));
- },
- );
- }
-
- double getFieldWidth(String fieldName) {
- final field = find.byWidgetPredicate(
- (widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName,
- );
-
- return getSize(field).width;
- }
-
Future findDateEditor(dynamic matcher) async {
final finder = find.byType(DateCellEditor);
expect(finder, matcher);
@@ -1596,7 +1572,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
of: textField,
matching: find.byWidgetPredicate(
(widget) =>
- widget is FlowySvg && widget.svg == FlowySvgs.close_filled_s,
+ widget is FlowySvg && widget.svg == FlowySvgs.close_filled_m,
),
),
);
diff --git a/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart b/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart
index 398a3f9657..491ac9432c 100644
--- a/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart
@@ -307,11 +307,9 @@ class EditorOperations {
Future openTurnIntoMenu(Path path) async {
await hoverAndClickOptionMenuButton(path);
await tester.tapButton(
- find
- .findTextInFlowyText(
- LocaleKeys.document_plugins_optionAction_turnInto.tr(),
- )
- .first,
+ find.findTextInFlowyText(
+ LocaleKeys.document_plugins_optionAction_turnInto.tr(),
+ ),
);
await tester.pumpUntilFound(find.byType(TurnIntoOptionMenu));
}
diff --git a/frontend/appflowy_flutter/integration_test/shared/settings.dart b/frontend/appflowy_flutter/integration_test/shared/settings.dart
index bfc5efedde..aade7bb4c9 100644
--- a/frontend/appflowy_flutter/integration_test/shared/settings.dart
+++ b/frontend/appflowy_flutter/integration_test/shared/settings.dart
@@ -79,7 +79,7 @@ extension AppFlowySettings on WidgetTester {
// Enable editing username
final editUsernameFinder = find.descendant(
of: find.byType(AccountUserProfile),
- matching: find.byFlowySvg(FlowySvgs.toolbar_link_edit_m),
+ matching: find.byFlowySvg(FlowySvgs.edit_s),
);
await tap(editUsernameFinder, warnIfMissed: false);
await pumpAndSettle();
diff --git a/frontend/appflowy_flutter/lib/ai/ai.dart b/frontend/appflowy_flutter/lib/ai/ai.dart
index 9bfeeb4e00..e3f52a8168 100644
--- a/frontend/appflowy_flutter/lib/ai/ai.dart
+++ b/frontend/appflowy_flutter/lib/ai/ai.dart
@@ -2,8 +2,6 @@ export 'service/ai_entities.dart';
export 'service/ai_prompt_input_bloc.dart';
export 'service/appflowy_ai_service.dart';
export 'service/error.dart';
-export 'service/ai_model_state_notifier.dart';
-export 'service/select_model_bloc.dart';
export 'widgets/loading_indicator.dart';
export 'widgets/prompt_input/action_buttons.dart';
export 'widgets/prompt_input/desktop_prompt_text_field.dart';
@@ -15,5 +13,4 @@ export 'widgets/prompt_input/mentioned_page_text_span.dart';
export 'widgets/prompt_input/predefined_format_buttons.dart';
export 'widgets/prompt_input/select_sources_bottom_sheet.dart';
export 'widgets/prompt_input/select_sources_menu.dart';
-export 'widgets/prompt_input/select_model_menu.dart';
export 'widgets/prompt_input/send_button.dart';
diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart b/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart
index b08fadb7f8..b8592bc32b 100644
--- a/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart
+++ b/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart
@@ -4,28 +4,6 @@ import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:equatable/equatable.dart';
-class AIStreamEventPrefix {
- static const data = 'data:';
- static const error = 'error:';
- static const metadata = 'metadata:';
- static const start = 'start:';
- static const finish = 'finish:';
- static const comment = 'comment:';
- static const aiResponseLimit = 'AI_RESPONSE_LIMIT';
- static const aiImageResponseLimit = 'AI_IMAGE_RESPONSE_LIMIT';
- static const aiMaxRequired = 'AI_MAX_REQUIRED:';
- static const localAINotReady = 'LOCAL_AI_NOT_READY';
- static const localAIDisabled = 'LOCAL_AI_DISABLED';
-}
-
-enum AiType {
- cloud,
- local;
-
- bool get isCloud => this == cloud;
- bool get isLocal => this == local;
-}
-
class PredefinedFormat extends Equatable {
const PredefinedFormat({
required this.imageFormat,
diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart b/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart
deleted file mode 100644
index 0bcc41da9b..0000000000
--- a/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart
+++ /dev/null
@@ -1,181 +0,0 @@
-import 'package:appflowy/ai/ai.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/ai_chat/application/ai_model_switch_listener.dart';
-import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/log.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
-import 'package:appflowy_result/appflowy_result.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:protobuf/protobuf.dart';
-import 'package:universal_platform/universal_platform.dart';
-
-typedef OnModelStateChangedCallback = void Function(AiType, bool, String);
-typedef OnAvailableModelsChangedCallback = void Function(
- List,
- AIModelPB?,
-);
-
-class AIModelStateNotifier {
- AIModelStateNotifier({required this.objectId})
- : _localAIListener =
- UniversalPlatform.isDesktop ? LocalAIStateListener() : null,
- _aiModelSwitchListener = AIModelSwitchListener(objectId: objectId) {
- _startListening();
- _init();
- }
-
- final String objectId;
- final LocalAIStateListener? _localAIListener;
- final AIModelSwitchListener _aiModelSwitchListener;
- LocalAIPB? _localAIState;
- AvailableModelsPB? _availableModels;
-
- // callbacks
- final List _stateChangedCallbacks = [];
- final List
- _availableModelsChangedCallbacks = [];
-
- void _startListening() {
- if (UniversalPlatform.isDesktop) {
- _localAIListener?.start(
- stateCallback: (state) async {
- _localAIState = state;
- _notifyStateChanged();
-
- if (state.state == RunningStatePB.Running ||
- state.state == RunningStatePB.Stopped) {
- await _loadAvailableModels();
- _notifyAvailableModelsChanged();
- }
- },
- );
- }
-
- _aiModelSwitchListener.start(
- onUpdateSelectedModel: (model) async {
- final updatedModels = _availableModels?.deepCopy()
- ?..selectedModel = model;
- _availableModels = updatedModels;
- _notifyAvailableModelsChanged();
-
- if (model.isLocal && UniversalPlatform.isDesktop) {
- await _loadLocalAiState();
- }
- _notifyStateChanged();
- },
- );
- }
-
- void _init() async {
- await Future.wait([_loadLocalAiState(), _loadAvailableModels()]);
- _notifyStateChanged();
- _notifyAvailableModelsChanged();
- }
-
- void addListener({
- OnModelStateChangedCallback? onStateChanged,
- OnAvailableModelsChangedCallback? onAvailableModelsChanged,
- }) {
- if (onStateChanged != null) {
- _stateChangedCallbacks.add(onStateChanged);
- }
- if (onAvailableModelsChanged != null) {
- _availableModelsChangedCallbacks.add(onAvailableModelsChanged);
- }
- }
-
- void removeListener({
- OnModelStateChangedCallback? onStateChanged,
- OnAvailableModelsChangedCallback? onAvailableModelsChanged,
- }) {
- if (onStateChanged != null) {
- _stateChangedCallbacks.remove(onStateChanged);
- }
- if (onAvailableModelsChanged != null) {
- _availableModelsChangedCallbacks.remove(onAvailableModelsChanged);
- }
- }
-
- Future dispose() async {
- _stateChangedCallbacks.clear();
- _availableModelsChangedCallbacks.clear();
- await _localAIListener?.stop();
- await _aiModelSwitchListener.stop();
- }
-
- (AiType, String, bool) getState() {
- if (UniversalPlatform.isMobile) {
- return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true);
- }
-
- final availableModels = _availableModels;
- final localAiState = _localAIState;
-
- if (availableModels == null) {
- return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true);
- }
- if (localAiState == null) {
- Log.warn("Cannot get local AI state");
- return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true);
- }
-
- if (!availableModels.selectedModel.isLocal) {
- return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true);
- }
-
- final editable = localAiState.state == RunningStatePB.Running;
- final hintText = editable
- ? LocaleKeys.chat_inputLocalAIMessageHint.tr()
- : LocaleKeys.settings_aiPage_keys_localAIInitializing.tr();
-
- return (AiType.local, hintText, editable);
- }
-
- (List, AIModelPB?) getAvailableModels() {
- final availableModels = _availableModels;
- if (availableModels == null) {
- return ([], null);
- }
- return (availableModels.models, availableModels.selectedModel);
- }
-
- void _notifyAvailableModelsChanged() {
- final (models, selectedModel) = getAvailableModels();
- for (final callback in _availableModelsChangedCallbacks) {
- callback(models, selectedModel);
- }
- }
-
- void _notifyStateChanged() {
- final (type, hintText, isEditable) = getState();
- for (final callback in _stateChangedCallbacks) {
- callback(type, isEditable, hintText);
- }
- }
-
- Future _loadAvailableModels() {
- final payload = AvailableModelsQueryPB(source: objectId);
- return AIEventGetAvailableModels(payload).send().fold(
- (models) => _availableModels = models,
- (err) => Log.error("Failed to get available models: $err"),
- );
- }
-
- Future _loadLocalAiState() {
- return AIEventGetLocalAIState().send().fold(
- (localAIState) => _localAIState = localAIState,
- (error) => Log.error("Failed to get local AI state: $error"),
- );
- }
-}
-
-extension AiModelExtension on AIModelPB {
- bool get isDefault {
- return name == "Auto";
- }
-
- String get i18n {
- return isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name;
- }
-}
diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart b/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart
index 95854ab047..23d8879275 100644
--- a/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart
+++ b/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart
@@ -1,8 +1,12 @@
import 'dart:async';
-import 'package:appflowy/ai/service/ai_model_state_notifier.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
+import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
+import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -12,20 +16,19 @@ part 'ai_prompt_input_bloc.freezed.dart';
class AIPromptInputBloc extends Bloc {
AIPromptInputBloc({
- required String objectId,
required PredefinedFormat? predefinedFormat,
- }) : aiModelStateNotifier = AIModelStateNotifier(objectId: objectId),
+ }) : _listener = LocalLLMListener(),
super(AIPromptInputState.initial(predefinedFormat)) {
_dispatch();
_startListening();
_init();
}
- final AIModelStateNotifier aiModelStateNotifier;
+ final LocalLLMListener _listener;
@override
Future close() async {
- await aiModelStateNotifier.dispose();
+ await _listener.stop();
return super.close();
}
@@ -33,19 +36,42 @@ class AIPromptInputBloc extends Bloc {
on(
(event, emit) {
event.when(
- updateAIState: (aiType, editable, hintText) {
+ updateChatState: (LocalAIChatPB chatState) {
+ // Only user enable chat with file and the plugin is already running
+ final supportChatWithFile = chatState.fileEnabled &&
+ chatState.pluginState.state == RunningStatePB.Running;
+
+ final aiType = chatState.pluginState.state == RunningStatePB.Running
+ ? AIType.localAI
+ : AIType.appflowyAI;
+
emit(
state.copyWith(
aiType: aiType,
- editable: editable,
- hintText: hintText,
+ supportChatWithFile: supportChatWithFile,
+ chatState: chatState,
+ ),
+ );
+ },
+ updatePluginState: (LocalAIPluginStatePB chatState) {
+ final fileEnabled = state.chatState?.fileEnabled ?? false;
+ final supportChatWithFile =
+ fileEnabled && chatState.state == RunningStatePB.Running;
+
+ final aiType = chatState.state == RunningStatePB.Running
+ ? AIType.localAI
+ : AIType.appflowyAI;
+
+ emit(
+ state.copyWith(
+ supportChatWithFile: supportChatWithFile,
+ aiType: aiType,
),
);
},
toggleShowPredefinedFormat: () {
- final showPredefinedFormats = !state.showPredefinedFormats;
final predefinedFormat =
- showPredefinedFormats && state.predefinedFormat == null
+ !state.showPredefinedFormats && state.predefinedFormat == null
? PredefinedFormat(
imageFormat: ImageFormat.text,
textFormat: TextFormat.paragraph,
@@ -53,15 +79,12 @@ class AIPromptInputBloc extends Bloc {
: null;
emit(
state.copyWith(
- showPredefinedFormats: showPredefinedFormats,
+ showPredefinedFormats: !state.showPredefinedFormats,
predefinedFormat: predefinedFormat,
),
);
},
updatePredefinedFormat: (format) {
- if (!state.showPredefinedFormats) {
- return;
- }
emit(state.copyWith(predefinedFormat: format));
},
attachFile: (filePath, fileName) {
@@ -104,16 +127,29 @@ class AIPromptInputBloc extends Bloc {
}
void _startListening() {
- aiModelStateNotifier.addListener(
- onStateChanged: (aiType, editable, hintText) {
- add(AIPromptInputEvent.updateAIState(aiType, editable, hintText));
+ _listener.start(
+ stateCallback: (pluginState) {
+ if (!isClosed) {
+ add(AIPromptInputEvent.updatePluginState(pluginState));
+ }
+ },
+ chatStateCallback: (chatState) {
+ if (!isClosed) {
+ add(AIPromptInputEvent.updateChatState(chatState));
+ }
},
);
}
void _init() {
- final (aiType, hintText, isEditable) = aiModelStateNotifier.getState();
- add(AIPromptInputEvent.updateAIState(aiType, isEditable, hintText));
+ AIEventGetLocalAIChatState().send().fold(
+ (chatState) {
+ if (!isClosed) {
+ add(AIPromptInputEvent.updateChatState(chatState));
+ }
+ },
+ Log.error,
+ );
}
Map consumeMetadata() {
@@ -132,12 +168,12 @@ class AIPromptInputBloc extends Bloc {
@freezed
class AIPromptInputEvent with _$AIPromptInputEvent {
- const factory AIPromptInputEvent.updateAIState(
- AiType aiType,
- bool editable,
- String hintText,
- ) = _UpdateAIState;
-
+ const factory AIPromptInputEvent.updateChatState(
+ LocalAIChatPB chatState,
+ ) = _UpdateChatState;
+ const factory AIPromptInputEvent.updatePluginState(
+ LocalAIPluginStatePB chatState,
+ ) = _UpdatePluginState;
const factory AIPromptInputEvent.toggleShowPredefinedFormat() =
_ToggleShowPredefinedFormat;
const factory AIPromptInputEvent.updatePredefinedFormat(
@@ -156,25 +192,30 @@ class AIPromptInputEvent with _$AIPromptInputEvent {
@freezed
class AIPromptInputState with _$AIPromptInputState {
const factory AIPromptInputState({
- required AiType aiType,
+ required AIType aiType,
required bool supportChatWithFile,
required bool showPredefinedFormats,
required PredefinedFormat? predefinedFormat,
+ required LocalAIChatPB? chatState,
required List attachedFiles,
required List mentionedPages,
- required bool editable,
- required String hintText,
}) = _AIPromptInputState;
factory AIPromptInputState.initial(PredefinedFormat? format) =>
AIPromptInputState(
- aiType: AiType.cloud,
+ aiType: AIType.appflowyAI,
supportChatWithFile: false,
showPredefinedFormats: format != null,
predefinedFormat: format,
+ chatState: null,
attachedFiles: [],
mentionedPages: [],
- editable: true,
- hintText: '',
);
}
+
+enum AIType {
+ appflowyAI,
+ localAI;
+
+ bool get isLocalAI => this == localAI;
+}
diff --git a/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart b/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart
index 39487652f8..2709b1d59c 100644
--- a/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart
+++ b/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart
@@ -3,7 +3,6 @@ import 'dart:ffi';
import 'dart:isolate';
import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart';
import 'package:appflowy/shared/list_extension.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
@@ -15,26 +14,16 @@ import 'package:fixnum/fixnum.dart' as fixnum;
import 'ai_entities.dart';
import 'error.dart';
-enum LocalAIStreamingState {
- notReady,
- disabled,
-}
-
abstract class AIRepository {
Future streamCompletion({
String? objectId,
required String text,
PredefinedFormat? format,
- List sourceIds = const [],
- List history = const [],
required CompletionTypePB completionType,
required Future Function() onStart,
- required Future Function(String text) processMessage,
- required Future Function(String text) processAssistMessage,
+ required Future Function(String text) onProcess,
required Future Function() onEnd,
required void Function(AIError error) onError,
- required void Function(LocalAIStreamingState state)
- onLocalAIStreamingStateChange,
});
}
@@ -45,27 +34,19 @@ class AppFlowyAIService implements AIRepository {
required String text,
PredefinedFormat? format,
List sourceIds = const [],
- List history = const [],
required CompletionTypePB completionType,
required Future Function() onStart,
- required Future Function(String text) processMessage,
- required Future Function(String text) processAssistMessage,
+ required Future Function(String text) onProcess,
required Future Function() onEnd,
required void Function(AIError error) onError,
- required void Function(LocalAIStreamingState state)
- onLocalAIStreamingStateChange,
}) async {
final stream = AppFlowyCompletionStream(
onStart: onStart,
- processMessage: processMessage,
- processAssistMessage: processAssistMessage,
- processError: onError,
- onLocalAIStreamingStateChange: onLocalAIStreamingStateChange,
+ onProcess: onProcess,
onEnd: onEnd,
+ onError: onError,
);
- final records = history.map((record) => record.toPB()).toList();
-
final payload = CompleteTextPB(
text: text,
completionType: completionType,
@@ -76,7 +57,6 @@ class AppFlowyAIService implements AIRepository {
if (objectId != null) objectId,
...sourceIds,
].unique(),
- history: records,
);
return AIEventCompleteText(payload).send().fold(
@@ -92,30 +72,23 @@ class AppFlowyAIService implements AIRepository {
abstract class CompletionStream {
CompletionStream({
required this.onStart,
- required this.processMessage,
- required this.processAssistMessage,
- required this.processError,
- required this.onLocalAIStreamingStateChange,
+ required this.onProcess,
required this.onEnd,
+ required this.onError,
});
final Future Function() onStart;
- final Future Function(String text) processMessage;
- final Future Function(String text) processAssistMessage;
- final void Function(AIError error) processError;
- final void Function(LocalAIStreamingState state)
- onLocalAIStreamingStateChange;
+ final Future Function(String text) onProcess;
final Future Function() onEnd;
+ final void Function(AIError error) onError;
}
class AppFlowyCompletionStream extends CompletionStream {
AppFlowyCompletionStream({
required super.onStart,
- required super.processMessage,
- required super.processAssistMessage,
- required super.processError,
+ required super.onProcess,
required super.onEnd,
- required super.onLocalAIStreamingStateChange,
+ required super.onError,
}) {
_startListening();
}
@@ -129,7 +102,51 @@ class AppFlowyCompletionStream extends CompletionStream {
_port.handler = _controller.add;
_subscription = _controller.stream.listen(
(event) async {
- await _handleEvent(event);
+ if (event == "AI_RESPONSE_LIMIT") {
+ onError(
+ AIError(
+ message: LocaleKeys.ai_textLimitReachedDescription.tr(),
+ code: AIErrorCode.aiResponseLimitExceeded,
+ ),
+ );
+ }
+
+ if (event == "AI_IMAGE_RESPONSE_LIMIT") {
+ onError(
+ AIError(
+ message: LocaleKeys.ai_imageLimitReachedDescription.tr(),
+ code: AIErrorCode.aiImageResponseLimitExceeded,
+ ),
+ );
+ }
+
+ if (event.startsWith("AI_MAX_REQUIRED:")) {
+ final msg = event.substring(16);
+ onError(
+ AIError(
+ message: msg,
+ code: AIErrorCode.other,
+ ),
+ );
+ }
+
+ if (event.startsWith("start:")) {
+ await onStart();
+ }
+
+ if (event.startsWith("data:")) {
+ await onProcess(event.substring(5));
+ }
+
+ if (event.startsWith("finish:")) {
+ await onEnd();
+ }
+
+ if (event.startsWith("error:")) {
+ onError(
+ AIError(message: event.substring(6), code: AIErrorCode.other),
+ );
+ }
},
);
}
@@ -139,66 +156,4 @@ class AppFlowyCompletionStream extends CompletionStream {
await _subscription.cancel();
_port.close();
}
-
- Future _handleEvent(String event) async {
- // Check simple matches first
- if (event == AIStreamEventPrefix.aiResponseLimit) {
- processError(
- AIError(
- message: LocaleKeys.ai_textLimitReachedDescription.tr(),
- code: AIErrorCode.aiResponseLimitExceeded,
- ),
- );
- return;
- }
-
- if (event == AIStreamEventPrefix.aiImageResponseLimit) {
- processError(
- AIError(
- message: LocaleKeys.ai_imageLimitReachedDescription.tr(),
- code: AIErrorCode.aiImageResponseLimitExceeded,
- ),
- );
- return;
- }
-
- // Otherwise, parse out prefix:content
- if (event.startsWith(AIStreamEventPrefix.aiMaxRequired)) {
- processError(
- AIError(
- message: event.substring(AIStreamEventPrefix.aiMaxRequired.length),
- code: AIErrorCode.other,
- ),
- );
- } else if (event.startsWith(AIStreamEventPrefix.start)) {
- await onStart();
- } else if (event.startsWith(AIStreamEventPrefix.data)) {
- await processMessage(
- event.substring(AIStreamEventPrefix.data.length),
- );
- } else if (event.startsWith(AIStreamEventPrefix.comment)) {
- await processAssistMessage(
- event.substring(AIStreamEventPrefix.comment.length),
- );
- } else if (event.startsWith(AIStreamEventPrefix.finish)) {
- await onEnd();
- } else if (event.startsWith(AIStreamEventPrefix.localAIDisabled)) {
- onLocalAIStreamingStateChange(
- LocalAIStreamingState.disabled,
- );
- } else if (event.startsWith(AIStreamEventPrefix.localAINotReady)) {
- onLocalAIStreamingStateChange(
- LocalAIStreamingState.notReady,
- );
- } else if (event.startsWith(AIStreamEventPrefix.error)) {
- processError(
- AIError(
- message: event.substring(AIStreamEventPrefix.error.length),
- code: AIErrorCode.other,
- ),
- );
- } else {
- Log.debug('Unknown AI event: $event');
- }
- }
}
diff --git a/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart b/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart
deleted file mode 100644
index 7ad52b9ec4..0000000000
--- a/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart
+++ /dev/null
@@ -1,92 +0,0 @@
-import 'dart:async';
-
-import 'package:appflowy/ai/service/ai_model_state_notifier.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbserver.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:freezed_annotation/freezed_annotation.dart';
-
-part 'select_model_bloc.freezed.dart';
-
-class SelectModelBloc extends Bloc {
- SelectModelBloc({
- required AIModelStateNotifier aiModelStateNotifier,
- }) : _aiModelStateNotifier = aiModelStateNotifier,
- super(SelectModelState.initial(aiModelStateNotifier)) {
- on(
- (event, emit) {
- event.when(
- selectModel: (model) {
- AIEventUpdateSelectedModel(
- UpdateSelectedModelPB(
- source: _aiModelStateNotifier.objectId,
- selectedModel: model,
- ),
- ).send();
-
- emit(state.copyWith(selectedModel: model));
- },
- didLoadModels: (models, selectedModel) {
- emit(
- SelectModelState(
- models: models,
- selectedModel: selectedModel,
- ),
- );
- },
- );
- },
- );
-
- _aiModelStateNotifier.addListener(
- onAvailableModelsChanged: _onAvailableModelsChanged,
- );
- }
-
- final AIModelStateNotifier _aiModelStateNotifier;
-
- @override
- Future close() async {
- _aiModelStateNotifier.removeListener(
- onAvailableModelsChanged: _onAvailableModelsChanged,
- );
- await super.close();
- }
-
- void _onAvailableModelsChanged(
- List models,
- AIModelPB? selectedModel,
- ) {
- if (!isClosed) {
- add(SelectModelEvent.didLoadModels(models, selectedModel));
- }
- }
-}
-
-@freezed
-class SelectModelEvent with _$SelectModelEvent {
- const factory SelectModelEvent.selectModel(
- AIModelPB model,
- ) = _SelectModel;
-
- const factory SelectModelEvent.didLoadModels(
- List models,
- AIModelPB? selectedModel,
- ) = _DidLoadModels;
-}
-
-@freezed
-class SelectModelState with _$SelectModelState {
- const factory SelectModelState({
- required List models,
- required AIModelPB? selectedModel,
- }) = _SelectModelState;
-
- factory SelectModelState.initial(AIModelStateNotifier notifier) {
- final (models, selectedModel) = notifier.getAvailableModels();
- return SelectModelState(
- models: models,
- selectedModel: selectedModel,
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart b/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart
index 3a9c96b255..e21ea95ac1 100644
--- a/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart
+++ b/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart
@@ -16,50 +16,48 @@ class AILoadingIndicator extends StatelessWidget {
@override
Widget build(BuildContext context) {
final slice = Duration(milliseconds: duration.inMilliseconds ~/ 5);
- return SelectionContainer.disabled(
- child: SizedBox(
- height: 20,
- child: SeparatedRow(
- separatorBuilder: () => const HSpace(4),
- children: [
- Padding(
- padding: const EdgeInsetsDirectional.only(end: 4.0),
- child: FlowyText(
- text,
- color: Theme.of(context).hintColor,
- ),
+ return SizedBox(
+ height: 20,
+ child: SeparatedRow(
+ separatorBuilder: () => const HSpace(4),
+ children: [
+ Padding(
+ padding: const EdgeInsetsDirectional.only(end: 4.0),
+ child: FlowyText(
+ text,
+ color: Theme.of(context).hintColor,
),
- buildDot(const Color(0xFF9327FF))
- .animate(onPlay: (controller) => controller.repeat())
- .slideY(duration: slice, begin: 0, end: -1)
- .then()
- .slideY(begin: -1, end: 1)
- .then()
- .slideY(begin: 1, end: 0)
- .then()
- .slideY(duration: slice * 2, begin: 0, end: 0),
- buildDot(const Color(0xFFFB006D))
- .animate(onPlay: (controller) => controller.repeat())
- .slideY(duration: slice, begin: 0, end: 0)
- .then()
- .slideY(begin: 0, end: -1)
- .then()
- .slideY(begin: -1, end: 1)
- .then()
- .slideY(begin: 1, end: 0)
- .then()
- .slideY(begin: 0, end: 0),
- buildDot(const Color(0xFFFFCE00))
- .animate(onPlay: (controller) => controller.repeat())
- .slideY(duration: slice * 2, begin: 0, end: 0)
- .then()
- .slideY(duration: slice, begin: 0, end: -1)
- .then()
- .slideY(begin: -1, end: 1)
- .then()
- .slideY(begin: 1, end: 0),
- ],
- ),
+ ),
+ buildDot(const Color(0xFF9327FF))
+ .animate(onPlay: (controller) => controller.repeat())
+ .slideY(duration: slice, begin: 0, end: -1)
+ .then()
+ .slideY(begin: -1, end: 1)
+ .then()
+ .slideY(begin: 1, end: 0)
+ .then()
+ .slideY(duration: slice * 2, begin: 0, end: 0),
+ buildDot(const Color(0xFFFB006D))
+ .animate(onPlay: (controller) => controller.repeat())
+ .slideY(duration: slice, begin: 0, end: 0)
+ .then()
+ .slideY(begin: 0, end: -1)
+ .then()
+ .slideY(begin: -1, end: 1)
+ .then()
+ .slideY(begin: 1, end: 0)
+ .then()
+ .slideY(begin: 0, end: 0),
+ buildDot(const Color(0xFFFFCE00))
+ .animate(onPlay: (controller) => controller.repeat())
+ .slideY(duration: slice * 2, begin: 0, end: 0)
+ .then()
+ .slideY(duration: slice, begin: 0, end: -1)
+ .then()
+ .slideY(begin: -1, end: 1)
+ .then()
+ .slideY(begin: 1, end: 0),
+ ],
),
);
}
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart
index a2676f2c15..e2eeb80434 100644
--- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart
+++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart
@@ -17,26 +17,20 @@ class DesktopPromptInput extends StatefulWidget {
const DesktopPromptInput({
super.key,
required this.isStreaming,
- required this.textController,
required this.onStopStreaming,
required this.onSubmitted,
required this.selectedSourcesNotifier,
required this.onUpdateSelectedSources,
this.hideDecoration = false,
- this.hideFormats = false,
- this.extraBottomActionButton,
});
final bool isStreaming;
- final TextEditingController textController;
final void Function() onStopStreaming;
final void Function(String, PredefinedFormat?, Map)
onSubmitted;
final ValueNotifier> selectedSourcesNotifier;
final void Function(List) onUpdateSelectedSources;
final bool hideDecoration;
- final bool hideFormats;
- final Widget? extraBottomActionButton;
@override
State createState() => _DesktopPromptInputState();
@@ -48,6 +42,7 @@ class _DesktopPromptInputState extends State {
final overlayController = OverlayPortalController();
final inputControlCubit = ChatInputControlCubit();
final focusNode = FocusNode();
+ final textController = TextEditingController();
late SendButtonState sendButtonState;
bool isComposing = false;
@@ -56,19 +51,18 @@ class _DesktopPromptInputState extends State {
void initState() {
super.initState();
- widget.textController.addListener(handleTextControllerChanged);
- focusNode
- ..addListener(
- () {
- if (!widget.hideDecoration) {
- setState(() {}); // refresh border color
- }
- if (!focusNode.hasFocus) {
- cancelMentionPage(); // hide menu when lost focus
- }
- },
- )
- ..onKeyEvent = handleKeyEvent;
+ textController.addListener(handleTextControllerChanged);
+
+ focusNode.addListener(
+ () {
+ if (!widget.hideDecoration) {
+ setState(() {}); // refresh border color
+ }
+ if (!focusNode.hasFocus) {
+ cancelMentionPage(); // hide menu when lost focus
+ }
+ },
+ );
updateSendButtonState();
@@ -86,7 +80,7 @@ class _DesktopPromptInputState extends State {
@override
void dispose() {
focusNode.dispose();
- widget.textController.removeListener(handleTextControllerChanged);
+ textController.dispose();
inputControlCubit.close();
super.dispose();
}
@@ -111,7 +105,7 @@ class _DesktopPromptInputState extends State {
overlayChildBuilder: (context) {
return PromptInputMentionPageMenu(
anchor: PromptInputAnchor(textFieldKey, layerLink),
- textController: widget.textController,
+ textController: textController,
onPageSelected: handlePageSelected,
);
},
@@ -141,11 +135,11 @@ class _DesktopPromptInputState extends State {
children: [
ConstrainedBox(
constraints: getTextFieldConstraints(
- state.showPredefinedFormats && !widget.hideFormats,
+ state.showPredefinedFormats,
),
child: inputTextField(),
),
- if (state.showPredefinedFormats && !widget.hideFormats)
+ if (state.showPredefinedFormats)
Positioned.fill(
bottom: null,
child: TextFieldTapRegion(
@@ -154,13 +148,14 @@ class _DesktopPromptInputState extends State {
start: 8.0,
),
child: ChangeFormatBar(
- showImageFormats: state.aiType.isCloud,
predefinedFormat: state.predefinedFormat,
spacing: 4.0,
onSelectPredefinedFormat: (format) =>
context.read().add(
AIPromptInputEvent
- .updatePredefinedFormat(format),
+ .updatePredefinedFormat(
+ format,
+ ),
),
),
),
@@ -170,9 +165,8 @@ class _DesktopPromptInputState extends State {
top: null,
child: TextFieldTapRegion(
child: _PromptBottomActions(
- showPredefinedFormatBar:
+ showPredefinedFormats:
state.showPredefinedFormats,
- showPredefinedFormatButton: !widget.hideFormats,
onTogglePredefinedFormatSection: () =>
context.read().add(
AIPromptInputEvent
@@ -186,8 +180,6 @@ class _DesktopPromptInputState extends State {
widget.selectedSourcesNotifier,
onUpdateSelectedSources:
widget.onUpdateSelectedSources,
- extraBottomActionButton:
- widget.extraBottomActionButton,
),
),
),
@@ -226,12 +218,12 @@ class _DesktopPromptInputState extends State {
if (!focusNode.hasFocus) {
focusNode.requestFocus();
}
- widget.textController.text += '@';
+ textController.text += '@';
WidgetsBinding.instance.addPostFrameCallback((_) {
if (context.mounted) {
context
.read()
- .startSearching(widget.textController.value);
+ .startSearching(textController.value);
overlayController.show();
}
});
@@ -247,7 +239,7 @@ class _DesktopPromptInputState extends State {
void updateSendButtonState() {
if (widget.isStreaming) {
sendButtonState = SendButtonState.streaming;
- } else if (widget.textController.text.trim().isEmpty) {
+ } else if (textController.text.trim().isEmpty) {
sendButtonState = SendButtonState.disabled;
} else {
sendButtonState = SendButtonState.enabled;
@@ -259,9 +251,9 @@ class _DesktopPromptInputState extends State {
return;
}
final trimmedText = inputControlCubit.formatIntputText(
- widget.textController.text.trim(),
+ textController.text.trim(),
);
- widget.textController.clear();
+ textController.clear();
if (trimmedText.isEmpty) {
return;
}
@@ -284,17 +276,17 @@ class _DesktopPromptInputState extends State {
setState(() {
// update whether send button is clickable
updateSendButtonState();
- isComposing = !widget.textController.value.composing.isCollapsed;
+ isComposing = !textController.value.composing.isCollapsed;
});
if (isComposing) {
return;
}
+ // handle text and selection changes ONLY when mentioning a page
+
// disable mention
return;
-
- // handle text and selection changes ONLY when mentioning a page
// ignore: dead_code
if (!overlayController.isShowing ||
inputControlCubit.filterStartPosition == -1) {
@@ -302,7 +294,6 @@ class _DesktopPromptInputState extends State {
}
// handle cases where mention a page is cancelled
- final textController = widget.textController;
final textSelection = textController.value.selection;
final isSelectingMultipleCharacters = !textSelection.isCollapsed;
final isCaretBeforeStartOfRange =
@@ -349,27 +340,22 @@ class _DesktopPromptInputState extends State {
}
KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) {
- // if (event.character == '@') {
- // WidgetsBinding.instance.addPostFrameCallback((_) {
- // inputControlCubit.startSearching(widget.textController.value);
- // overlayController.show();
- // });
- // }
- if (event is KeyDownEvent &&
- event.logicalKey == LogicalKeyboardKey.escape) {
- node.unfocus();
- return KeyEventResult.handled;
+ if (event.character == '@') {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ inputControlCubit.startSearching(textController.value);
+ overlayController.show();
+ });
}
return KeyEventResult.ignored;
}
void handlePageSelected(ViewPB view) {
- final newText = widget.textController.text.replaceRange(
+ final newText = textController.text.replaceRange(
inputControlCubit.filterStartPosition,
inputControlCubit.filterEndPosition,
view.id,
);
- widget.textController.value = TextEditingValue(
+ textController.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(
offset: inputControlCubit.filterStartPosition + view.id.length,
@@ -390,27 +376,18 @@ class _DesktopPromptInputState extends State {
link: layerLink,
child: BlocBuilder(
builder: (context, state) {
- Widget textField = PromptInputTextField(
+ return PromptInputTextField(
key: textFieldKey,
- editable: state.editable,
cubit: inputControlCubit,
- textController: widget.textController,
+ textController: textController,
textFieldFocusNode: focusNode,
contentPadding:
calculateContentPadding(state.showPredefinedFormats),
- hintText: state.hintText,
+ hintText: switch (state.aiType) {
+ AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(),
+ AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr()
+ },
);
-
- if (!state.editable) {
- textField = FlowyTooltip(
- message: LocaleKeys
- .settings_aiPage_keys_localAINotReadyTextFieldPrompt
- .tr(),
- child: textField,
- );
- }
-
- return textField;
},
),
),
@@ -515,7 +492,6 @@ class _FocusNextItemIntent extends Intent {
class PromptInputTextField extends StatelessWidget {
const PromptInputTextField({
super.key,
- required this.editable,
required this.cubit,
required this.textController,
required this.textFieldFocusNode,
@@ -527,7 +503,6 @@ class PromptInputTextField extends StatelessWidget {
final TextEditingController textController;
final FocusNode textFieldFocusNode;
final EdgeInsetsGeometry contentPadding;
- final bool editable;
final String hintText;
@override
@@ -535,8 +510,6 @@ class PromptInputTextField extends StatelessWidget {
return ExtendedTextField(
controller: textController,
focusNode: textFieldFocusNode,
- readOnly: !editable,
- enabled: editable,
decoration: InputDecoration(
border: InputBorder.none,
enabledBorder: InputBorder.none,
@@ -574,19 +547,16 @@ class PromptInputTextField extends StatelessWidget {
class _PromptBottomActions extends StatelessWidget {
const _PromptBottomActions({
required this.sendButtonState,
- required this.showPredefinedFormatBar,
- required this.showPredefinedFormatButton,
+ required this.showPredefinedFormats,
required this.onTogglePredefinedFormatSection,
required this.onStartMention,
required this.onSendPressed,
required this.onStopStreaming,
required this.selectedSourcesNotifier,
required this.onUpdateSelectedSources,
- this.extraBottomActionButton,
});
- final bool showPredefinedFormatBar;
- final bool showPredefinedFormatButton;
+ final bool showPredefinedFormats;
final void Function() onTogglePredefinedFormatSection;
final void Function() onStartMention;
final SendButtonState sendButtonState;
@@ -594,7 +564,6 @@ class _PromptBottomActions extends StatelessWidget {
final void Function() onStopStreaming;
final ValueNotifier> selectedSourcesNotifier;
final void Function(List) onUpdateSelectedSources;
- final Widget? extraBottomActionButton;
@override
Widget build(BuildContext context) {
@@ -603,27 +572,18 @@ class _PromptBottomActions extends StatelessWidget {
margin: DesktopAIChatSizes.inputActionBarMargin,
child: BlocBuilder(
builder: (context, state) {
+ if (state.chatState == null) {
+ return Align(
+ alignment: AlignmentDirectional.centerEnd,
+ child: _sendButton(),
+ );
+ }
return Row(
children: [
- if (showPredefinedFormatButton) ...[
- _predefinedFormatButton(),
- const HSpace(
- DesktopAIChatSizes.inputActionBarButtonSpacing,
- ),
- ],
- SelectModelMenu(
- aiModelStateNotifier:
- context.read().aiModelStateNotifier,
- ),
+ _predefinedFormatButton(),
const Spacer(),
- if (state.aiType.isCloud) ...[
- _selectSourcesButton(),
- const HSpace(
- DesktopAIChatSizes.inputActionBarButtonSpacing,
- ),
- ],
- if (extraBottomActionButton != null) ...[
- extraBottomActionButton!,
+ if (state.aiType == AIType.appflowyAI) ...[
+ _selectSourcesButton(context),
const HSpace(
DesktopAIChatSizes.inputActionBarButtonSpacing,
),
@@ -648,12 +608,12 @@ class _PromptBottomActions extends StatelessWidget {
Widget _predefinedFormatButton() {
return PromptInputDesktopToggleFormatButton(
- showFormatBar: showPredefinedFormatBar,
+ showFormatBar: showPredefinedFormats,
onTap: onTogglePredefinedFormatSection,
);
}
- Widget _selectSourcesButton() {
+ Widget _selectSourcesButton(BuildContext context) {
return PromptInputDesktopSelectSourcesButton(
onUpdateSelectedSources: onUpdateSelectedSources,
selectedSourcesNotifier: selectedSourcesNotifier,
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart
index 403b978905..189882ae4d 100644
--- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart
+++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart
@@ -48,30 +48,25 @@ class ChangeFormatBar extends StatelessWidget {
required this.predefinedFormat,
required this.spacing,
required this.onSelectPredefinedFormat,
- this.showImageFormats = true,
});
final PredefinedFormat? predefinedFormat;
final double spacing;
final void Function(PredefinedFormat) onSelectPredefinedFormat;
- final bool showImageFormats;
@override
Widget build(BuildContext context) {
- final showTextFormats = predefinedFormat?.imageFormat.hasText ?? true;
return SizedBox(
height: DesktopAIPromptSizes.predefinedFormatButtonHeight,
child: SeparatedRow(
mainAxisSize: MainAxisSize.min,
separatorBuilder: () => HSpace(spacing),
children: [
- if (showImageFormats) ...[
- _buildFormatButton(context, ImageFormat.text),
- _buildFormatButton(context, ImageFormat.textAndImage),
- _buildFormatButton(context, ImageFormat.image),
- ],
- if (showImageFormats && showTextFormats) _buildDivider(),
- if (showTextFormats) ...[
+ _buildFormatButton(context, ImageFormat.text),
+ _buildFormatButton(context, ImageFormat.textAndImage),
+ _buildFormatButton(context, ImageFormat.image),
+ if (predefinedFormat?.imageFormat.hasText ?? true) ...[
+ _buildDivider(),
_buildTextFormatButton(context, TextFormat.paragraph),
_buildTextFormatButton(context, TextFormat.bulletList),
_buildTextFormatButton(context, TextFormat.numberedList),
@@ -104,7 +99,6 @@ class ChangeFormatBar extends StatelessWidget {
},
child: FlowyTooltip(
message: format.i18n,
- preferBelow: false,
child: SizedBox.square(
dimension: _buttonSize,
child: FlowyHover(
@@ -151,7 +145,6 @@ class ChangeFormatBar extends StatelessWidget {
},
child: FlowyTooltip(
message: format.i18n,
- preferBelow: false,
child: SizedBox.square(
dimension: _buttonSize,
child: FlowyHover(
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart
deleted file mode 100644
index a611d84310..0000000000
--- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart
+++ /dev/null
@@ -1,264 +0,0 @@
-import 'package:appflowy/ai/ai.dart';
-import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
-import 'package:easy_localization/easy_localization.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:flutter_bloc/flutter_bloc.dart';
-
-class SelectModelMenu extends StatefulWidget {
- const SelectModelMenu({
- super.key,
- required this.aiModelStateNotifier,
- });
-
- final AIModelStateNotifier aiModelStateNotifier;
-
- @override
- State createState() => _SelectModelMenuState();
-}
-
-class _SelectModelMenuState extends State {
- final popoverController = PopoverController();
-
- @override
- Widget build(BuildContext context) {
- return BlocProvider(
- create: (context) => SelectModelBloc(
- aiModelStateNotifier: widget.aiModelStateNotifier,
- ),
- child: BlocBuilder(
- builder: (context, state) {
- return AppFlowyPopover(
- offset: Offset(-12.0, 0.0),
- constraints: BoxConstraints(maxWidth: 250, maxHeight: 600),
- direction: PopoverDirection.topWithLeftAligned,
- margin: EdgeInsets.zero,
- controller: popoverController,
- popupBuilder: (popoverContext) {
- return SelectModelPopoverContent(
- models: state.models,
- selectedModel: state.selectedModel,
- onSelectModel: (model) {
- if (model != state.selectedModel) {
- context
- .read()
- .add(SelectModelEvent.selectModel(model));
- }
- popoverController.close();
- },
- );
- },
- child: _CurrentModelButton(
- model: state.selectedModel,
- onTap: () {
- if (state.selectedModel != null) {
- popoverController.show();
- }
- },
- ),
- );
- },
- ),
- );
- }
-}
-
-class SelectModelPopoverContent extends StatelessWidget {
- const SelectModelPopoverContent({
- super.key,
- required this.models,
- required this.selectedModel,
- this.onSelectModel,
- });
-
- final List models;
- final AIModelPB? selectedModel;
- final void Function(AIModelPB)? onSelectModel;
-
- @override
- Widget build(BuildContext context) {
- if (models.isEmpty) {
- return const SizedBox.shrink();
- }
-
- // separate models into local and cloud models
- final localModels = models.where((model) => model.isLocal).toList();
- final cloudModels = models.where((model) => !model.isLocal).toList();
-
- return Padding(
- padding: const EdgeInsets.all(8.0),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (localModels.isNotEmpty) ...[
- _ModelSectionHeader(
- title: LocaleKeys.chat_switchModel_localModel.tr(),
- ),
- const VSpace(4.0),
- ],
- ...localModels.map(
- (model) => _ModelItem(
- model: model,
- isSelected: model == selectedModel,
- onTap: () => onSelectModel?.call(model),
- ),
- ),
- if (cloudModels.isNotEmpty && localModels.isNotEmpty) ...[
- const VSpace(8.0),
- _ModelSectionHeader(
- title: LocaleKeys.chat_switchModel_cloudModel.tr(),
- ),
- const VSpace(4.0),
- ],
- ...cloudModels.map(
- (model) => _ModelItem(
- model: model,
- isSelected: model == selectedModel,
- onTap: () => onSelectModel?.call(model),
- ),
- ),
- ],
- ),
- );
- }
-}
-
-class _ModelSectionHeader extends StatelessWidget {
- const _ModelSectionHeader({
- required this.title,
- });
-
- final String title;
-
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(top: 4, bottom: 2),
- child: FlowyText(
- title,
- fontSize: 12,
- figmaLineHeight: 16,
- color: Theme.of(context).hintColor,
- fontWeight: FontWeight.w500,
- ),
- );
- }
-}
-
-class _ModelItem extends StatelessWidget {
- const _ModelItem({
- required this.model,
- required this.isSelected,
- required this.onTap,
- });
-
- final AIModelPB model;
- final bool isSelected;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- return ConstrainedBox(
- constraints: const BoxConstraints(minHeight: 32),
- child: FlowyButton(
- onTap: onTap,
- margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
- text: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- FlowyText(
- model.i18n,
- figmaLineHeight: 20,
- overflow: TextOverflow.ellipsis,
- ),
- if (model.desc.isNotEmpty)
- FlowyText(
- model.desc,
- fontSize: 12,
- figmaLineHeight: 16,
- color: Theme.of(context).hintColor,
- overflow: TextOverflow.ellipsis,
- ),
- ],
- ),
- rightIcon: isSelected
- ? FlowySvg(
- FlowySvgs.check_s,
- size: const Size.square(20),
- color: Theme.of(context).colorScheme.primary,
- )
- : null,
- ),
- );
- }
-}
-
-class _CurrentModelButton extends StatelessWidget {
- const _CurrentModelButton({
- required this.model,
- required this.onTap,
- });
-
- final AIModelPB? model;
- final VoidCallback onTap;
-
- @override
- Widget build(BuildContext context) {
- return FlowyTooltip(
- message: LocaleKeys.chat_switchModel_label.tr(),
- child: GestureDetector(
- onTap: onTap,
- behavior: HitTestBehavior.opaque,
- child: SizedBox(
- height: DesktopAIPromptSizes.actionBarButtonSize,
- child: AnimatedSize(
- duration: const Duration(milliseconds: 50),
- curve: Curves.easeInOut,
- alignment: AlignmentDirectional.centerStart,
- child: FlowyHover(
- style: const HoverStyle(
- borderRadius: BorderRadius.all(Radius.circular(8)),
- ),
- child: Padding(
- padding: const EdgeInsetsDirectional.all(4.0),
- child: Row(
- children: [
- Padding(
- // TODO: remove this after change icon to 20px
- padding: EdgeInsets.all(2),
- child: FlowySvg(
- FlowySvgs.ai_sparks_s,
- color: Theme.of(context).hintColor,
- size: Size.square(16),
- ),
- ),
- if (model != null && !model!.isDefault)
- Padding(
- padding: EdgeInsetsDirectional.only(end: 2.0),
- child: FlowyText(
- model!.i18n,
- fontSize: 12,
- figmaLineHeight: 16,
- color: Theme.of(context).hintColor,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- FlowySvg(
- FlowySvgs.ai_source_drop_down_s,
- color: Theme.of(context).hintColor,
- size: const Size.square(8),
- ),
- ],
- ),
- ),
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart
index 51357e6a0b..d7c920c49c 100644
--- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart
+++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart
@@ -145,7 +145,7 @@ class _IndicatorButton extends StatelessWidget {
children: [
FlowySvg(
FlowySvgs.ai_page_s,
- color: Theme.of(context).hintColor,
+ color: Theme.of(context).iconTheme.color,
),
const HSpace(2.0),
ValueListenableBuilder(
@@ -170,7 +170,7 @@ class _IndicatorButton extends StatelessWidget {
FlowySvg(
FlowySvgs.ai_source_drop_down_s,
color: Theme.of(context).hintColor,
- size: const Size.square(8),
+ size: const Size.square(10),
),
],
),
diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart
index cca6e65f63..20def4ca5e 100644
--- a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart
+++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart
@@ -1,6 +1,4 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:universal_platform/universal_platform.dart';
@@ -25,23 +23,6 @@ class PromptInputSendButton extends StatelessWidget {
Widget build(BuildContext context) {
return FlowyIconButton(
width: _buttonSize,
- richTooltipText: switch (state) {
- SendButtonState.streaming => TextSpan(
- children: [
- TextSpan(
- text: '${LocaleKeys.chat_stopTooltip.tr()} ',
- style: context.tooltipTextStyle(),
- ),
- TextSpan(
- text: 'ESC',
- style: context
- .tooltipTextStyle()
- ?.copyWith(color: Theme.of(context).hintColor),
- ),
- ],
- ),
- _ => null,
- },
icon: switch (state) {
SendButtonState.enabled => FlowySvg(
FlowySvgs.ai_send_filled_s,
diff --git a/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart
index fd8aa03dfe..a27ab07e9d 100644
--- a/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart
+++ b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart
@@ -44,27 +44,17 @@ Future afLaunchUri(
uri = Uri.parse('https://$url');
}
- /// opening an incorrect link will cause a system error dialog to pop up on macOS
- /// only use [canLaunchUrl] on macOS
- /// and there is an known issue with url_launcher on Linux where it fails to launch
- /// see https://github.com/flutter/flutter/issues/88463
- bool result = true;
- if (UniversalPlatform.isMacOS) {
- result = await launcher.canLaunchUrl(uri);
- }
-
- if (result) {
- try {
- // try to launch the uri directly
- result = await launcher.launchUrl(
- uri,
- mode: mode,
- webOnlyWindowName: webOnlyWindowName,
- );
- } on PlatformException catch (e) {
- Log.error('Failed to open uri: $e');
- return false;
- }
+ // try to launch the uri directly
+ bool result;
+ try {
+ result = await launcher.launchUrl(
+ uri,
+ mode: mode,
+ webOnlyWindowName: webOnlyWindowName,
+ );
+ } on PlatformException catch (e) {
+ Log.error('Failed to open uri: $e');
+ return false;
}
// if the uri is not a valid url, try to launch it with http scheme
@@ -143,6 +133,7 @@ Future _afLaunchLocalUri(
};
if (context != null && context.mounted) {
showToastNotification(
+ context,
message: message,
type: result.type == ResultType.done
? ToastificationType.success
diff --git a/frontend/appflowy_flutter/lib/env/cloud_env.dart b/frontend/appflowy_flutter/lib/env/cloud_env.dart
index 15f3ada42e..986fab128b 100644
--- a/frontend/appflowy_flutter/lib/env/cloud_env.dart
+++ b/frontend/appflowy_flutter/lib/env/cloud_env.dart
@@ -100,10 +100,6 @@ bool get isAuthEnabled {
return false;
}
-bool get isLocalAuthEnabled {
- return currentCloudType().isLocal;
-}
-
/// Determines if AppFlowy Cloud is enabled.
bool get isAppFlowyCloudEnabled {
return currentCloudType().isAppFlowyCloudEnabled;
diff --git a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
index 157be012b1..56a61e120b 100644
--- a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
+++ b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart
@@ -16,8 +16,6 @@ const double _kMinimumWidth = 112.0;
const double _kDefaultHorizontalPadding = 12.0;
-typedef CompareFunction = bool Function(T? left, T? right);
-
// Navigation shortcuts to move the selected menu items up or down.
final Map _kMenuTraversalShortcuts =
{
@@ -88,7 +86,6 @@ class AFDropdownMenu extends StatefulWidget {
this.requestFocusOnTap,
this.expandedInsets,
this.searchCallback,
- this.selectOptionCompare,
required this.dropdownMenuEntries,
});
@@ -270,11 +267,6 @@ class AFDropdownMenu extends StatefulWidget {
/// which contains the contents of the text input field.
final SearchCallback? searchCallback;
- /// Defines the compare function for the menu items.
- ///
- /// Defaults to null. If this is null, the menu items will be sorted by the label.
- final CompareFunction? selectOptionCompare;
-
@override
State> createState() => _AFDropdownMenuState();
}
@@ -309,16 +301,7 @@ class _AFDropdownMenuState extends State> {
filteredEntries.any((DropdownMenuEntry entry) => entry.enabled);
final int index = filteredEntries.indexWhere(
- (DropdownMenuEntry entry) {
- if (widget.selectOptionCompare != null) {
- return widget.selectOptionCompare!(
- entry.value,
- widget.initialSelection,
- );
- } else {
- return entry.value == widget.initialSelection;
- }
- },
+ (DropdownMenuEntry entry) => entry.value == widget.initialSelection,
);
if (index != -1) {
_textEditingController.value = TextEditingValue(
diff --git a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
index 0527316860..1480cc02e9 100644
--- a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
+++ b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart
@@ -19,13 +19,14 @@ class UserProfileBloc extends Bloc {
Future _initialize(Emitter emit) async {
emit(const UserProfileState.loading());
- final latestOrFailure =
+
+ final workspaceOrFailure =
await FolderEventGetCurrentWorkspaceSetting().send();
final userOrFailure = await getIt().getUser();
- final latest = latestOrFailure.fold(
- (latestPB) => latestPB,
+ final workspaceSetting = workspaceOrFailure.fold(
+ (workspaceSettingPB) => workspaceSettingPB,
(error) => null,
);
@@ -34,13 +35,13 @@ class UserProfileBloc extends Bloc {
(error) => null,
);
- if (latest == null || userProfile == null) {
+ if (workspaceSetting == null || userProfile == null) {
return emit(const UserProfileState.workspaceFailure());
}
emit(
UserProfileState.success(
- workspaceSettings: latest,
+ workspaceSettings: workspaceSetting,
userProfile: userProfile,
),
);
@@ -58,7 +59,7 @@ class UserProfileState with _$UserProfileState {
const factory UserProfileState.loading() = _Loading;
const factory UserProfileState.workspaceFailure() = _WorkspaceFailure;
const factory UserProfileState.success({
- required WorkspaceLatestPB workspaceSettings,
+ required WorkspaceSettingPB workspaceSettings,
required UserProfilePB userProfile,
}) = _Success;
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
index 318b06394a..792679daf1 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart
@@ -336,6 +336,7 @@ class _MobileViewPageState extends State {
listener: (context, state) {
if (state.isLocked) {
showToastNotification(
+ context,
message: LocaleKeys.lockPage_pageLockedToast.tr(),
);
@@ -365,6 +366,7 @@ class _MobileViewPageState extends State {
listener: (context, state) {
if (state.isLocked) {
showToastNotification(
+ context,
message: LocaleKeys.lockPage_pageLockedToast.tr(),
);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
index be134e0a92..dd659420d6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart
@@ -66,7 +66,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
break;
case MobileViewBottomSheetBodyAction.delete:
context.read().add(const ViewEvent.delete());
- Navigator.of(context).pop();
+ context.pop();
break;
case MobileViewBottomSheetBodyAction.addToFavorites:
_addFavorite(context);
@@ -161,6 +161,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
context.pop();
showToastNotification(
+ context,
message: LocaleKeys.button_duplicateSuccessfully.tr(),
);
}
@@ -169,6 +170,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
_toggleFavorite(context);
showToastNotification(
+ context,
message: LocaleKeys.button_favoriteSuccessfully.tr(),
);
}
@@ -177,6 +179,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
_toggleFavorite(context);
showToastNotification(
+ context,
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
);
}
@@ -199,7 +202,8 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
),
);
showToastNotification(
- message: LocaleKeys.message_copy_success.tr(),
+ context,
+ message: LocaleKeys.grid_url_copy.tr(),
);
}
}
@@ -230,10 +234,12 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
),
);
showToastNotification(
+ context,
message: LocaleKeys.shareAction_copyLinkSuccess.tr(),
);
} else {
showToastNotification(
+ context,
message: LocaleKeys.shareAction_copyLinkToBlockFailed.tr(),
type: ToastificationType.error,
);
@@ -317,9 +323,11 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
if (state.publishResult != null) {
state.publishResult!.fold(
(value) => showToastNotification(
+ context,
message: LocaleKeys.publish_publishSuccessfully.tr(),
),
(error) => showToastNotification(
+ context,
message: '${LocaleKeys.publish_publishFailed.tr()}: ${error.code}',
type: ToastificationType.error,
),
@@ -327,9 +335,11 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
} else if (state.unpublishResult != null) {
state.unpublishResult!.fold(
(value) => showToastNotification(
+ context,
message: LocaleKeys.publish_unpublishSuccessfully.tr(),
),
(error) => showToastNotification(
+ context,
message: LocaleKeys.publish_unpublishFailed.tr(),
description: error.msg,
type: ToastificationType.error,
@@ -339,6 +349,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget {
state.updatePathNameResult!.onSuccess(
(value) {
showToastNotification(
+ context,
message:
LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(),
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
index 86021ea938..c1129af79d 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart
@@ -65,6 +65,7 @@ class _MobileViewItemBottomSheetState extends State {
Navigator.pop(context);
context.read().add(const ViewEvent.duplicate());
showToastNotification(
+ context,
message: LocaleKeys.button_duplicateSuccessfully.tr(),
);
break;
@@ -83,6 +84,7 @@ class _MobileViewItemBottomSheetState extends State {
.read()
.add(FavoriteEvent.toggle(widget.view));
showToastNotification(
+ context,
message: !widget.view.isFavorite
? LocaleKeys.button_favoriteSuccessfully.tr()
: LocaleKeys.button_unfavoriteSuccessfully.tr(),
@@ -144,6 +146,7 @@ class _MobileViewItemBottomSheetState extends State {
Navigator.pop(context);
showToastNotification(
+ context,
message: LocaleKeys.sideBar_removeSuccess.tr(),
);
},
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart
index 47ab37505e..991cf82b5d 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart
@@ -182,7 +182,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
),
_divider(),
..._buildPublishActions(context),
-
+ _divider(),
MobileQuickActionButton(
text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error,
@@ -203,7 +203,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
final userProfile = context.read().state.userProfilePB;
// the publish feature is only available for AppFlowy Cloud
if (userProfile == null ||
- userProfile.workspaceAuthType != AuthTypePB.Server) {
+ userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) {
return [];
}
@@ -236,7 +236,6 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileViewBottomSheetBodyAction.unpublish,
),
),
- _divider(),
];
} else {
return [
@@ -247,7 +246,6 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileViewBottomSheetBodyAction.publish,
),
),
- _divider(),
];
}
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
index d4b4292443..cb840b0f40 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart
@@ -45,6 +45,7 @@ enum MobilePaneActionType {
size: 24.0,
onPressed: (context) {
showToastNotification(
+ context,
message: LocaleKeys.button_unfavoriteSuccessfully.tr(),
);
@@ -60,6 +61,7 @@ enum MobilePaneActionType {
size: 24.0,
onPressed: (context) {
showToastNotification(
+ context,
message: LocaleKeys.button_favoriteSuccessfully.tr(),
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart
index b0f21188cd..fa3494002d 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart
@@ -103,7 +103,7 @@ class _OpenRowPageButtonState extends State {
Log.info('Open row page(${widget.documentId})');
if (view == null) {
- showToastNotification(message: 'Failed to open row page');
+ showToastNotification(context, message: 'Failed to open row page');
// reload the view again
unawaited(_preloadView(context));
Log.error('Failed to open row page(${widget.documentId})');
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
index 0e7a7cb4c6..e6d2d895b1 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart
@@ -31,9 +31,9 @@ class MobileFavoriteScreen extends StatelessWidget {
return const Center(child: CircularProgressIndicator.adaptive());
}
- final latest = snapshots.data?[0].fold(
- (latest) {
- return latest as WorkspaceLatestPB?;
+ final workspaceSetting = snapshots.data?[0].fold(
+ (workspaceSettingPB) {
+ return workspaceSettingPB as WorkspaceSettingPB?;
},
(error) => null,
);
@@ -46,7 +46,7 @@ class MobileFavoriteScreen extends StatelessWidget {
// In the unlikely case either of the above is null, eg.
// when a workspace is already open this can happen.
- if (latest == null || userProfile == null) {
+ if (workspaceSetting == null || userProfile == null) {
return const WorkspaceFailedScreen();
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
index 345a4591d1..2d409f58b6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart
@@ -44,9 +44,9 @@ class MobileHomeScreen extends StatelessWidget {
return const Center(child: CircularProgressIndicator.adaptive());
}
- final workspaceLatest = snapshots.data?[0].fold(
- (workspaceLatestPB) {
- return workspaceLatestPB as WorkspaceLatestPB?;
+ final workspaceSetting = snapshots.data?[0].fold(
+ (workspaceSettingPB) {
+ return workspaceSettingPB as WorkspaceSettingPB?;
},
(error) => null,
);
@@ -59,7 +59,7 @@ class MobileHomeScreen extends StatelessWidget {
// In the unlikely case either of the above is null, eg.
// when a workspace is already open this can happen.
- if (workspaceLatest == null || userProfile == null) {
+ if (workspaceSetting == null || userProfile == null) {
return const WorkspaceFailedScreen();
}
@@ -78,7 +78,7 @@ class MobileHomeScreen extends StatelessWidget {
value: userProfile,
child: MobileHomePage(
userProfile: userProfile,
- workspaceLatest: workspaceLatest,
+ workspaceSetting: workspaceSetting,
),
),
),
@@ -95,11 +95,11 @@ class MobileHomePage extends StatefulWidget {
const MobileHomePage({
super.key,
required this.userProfile,
- required this.workspaceLatest,
+ required this.workspaceSetting,
});
final UserProfilePB userProfile;
- final WorkspaceLatestPB workspaceLatest;
+ final WorkspaceSettingPB workspaceSetting;
@override
State createState() => _MobileHomePageState();
@@ -145,7 +145,7 @@ class _MobileHomePageState extends State {
void _onLatestViewChange() async {
final id = getIt().latestOpenView?.id;
- if (id == null || id.isEmpty) {
+ if (id == null) {
return;
}
await FolderEventSetLatestView(ViewIdPB(value: id)).send();
@@ -329,7 +329,7 @@ class _HomePageState extends State<_HomePage> {
}
if (message != null) {
- showToastNotification(message: message, type: toastType);
+ showToastNotification(context, message: message, type: toastType);
}
}
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart
index 113f12e543..97cc243c9e 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart
@@ -194,7 +194,6 @@ class _MobileWorkspace extends StatelessWidget {
context.read().add(
UserWorkspaceEvent.openWorkspace(
workspace.workspaceId,
- workspace.workspaceAuthType,
),
);
},
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
index a01df20549..b5845f763e 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart
@@ -71,6 +71,12 @@ class _MobileHomeSettingPageState extends State {
}
Widget _buildSettingsWidget(UserProfilePB userProfile) {
+ // show the third-party sign in buttons if user logged in with local session and auth is enabled.
+
+ final isLocalAuthEnabled =
+ userProfile.authenticator == AuthenticatorPB.Local && isAuthEnabled;
+ '';
+
return BlocProvider(
create: (context) => UserWorkspaceBloc(userProfile: userProfile)
..add(const UserWorkspaceEvent.initial()),
@@ -94,12 +100,13 @@ class _MobileHomeSettingPageState extends State {
key: ValueKey(currentWorkspaceId),
userProfile: userProfile,
workspaceId: currentWorkspaceId,
+ currentWorkspaceMemberRole: state.currentWorkspace?.role,
),
const SupportSettingGroup(),
const AboutSettingGroup(),
UserSessionSettingGroup(
userProfile: userProfile,
- showThirdPartyLogin: false,
+ showThirdPartyLogin: isLocalAuthEnabled,
),
const VSpace(20),
],
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart
index 659473a6b1..cbbda8362a 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart
@@ -16,7 +16,6 @@ enum _MobileSettingsPopupMenuItem {
members,
trash,
help,
- helpAndDocumentation,
}
class HomePageSettingsPopupMenu extends StatelessWidget {
@@ -48,7 +47,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
text: LocaleKeys.settings_popupMenuItem_settings.tr(),
),
// only show the member items in cloud mode
- if (userProfile.workspaceAuthType == AuthTypePB.Server) ...[
+ if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[
const PopupMenuDivider(height: 0.5),
_buildItem(
value: _MobileSettingsPopupMenuItem.members,
@@ -63,16 +62,10 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
text: LocaleKeys.settings_popupMenuItem_trash.tr(),
),
const PopupMenuDivider(height: 0.5),
- _buildItem(
- value: _MobileSettingsPopupMenuItem.helpAndDocumentation,
- svg: FlowySvgs.help_and_documentation_s,
- text: LocaleKeys.settings_popupMenuItem_helpAndDocumentation.tr(),
- ),
- const PopupMenuDivider(height: 0.5),
_buildItem(
value: _MobileSettingsPopupMenuItem.help,
svg: FlowySvgs.message_support_s,
- text: LocaleKeys.settings_popupMenuItem_getSupport.tr(),
+ text: LocaleKeys.settings_popupMenuItem_helpAndSupport.tr(),
),
],
onSelected: (_MobileSettingsPopupMenuItem value) {
@@ -89,9 +82,6 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
case _MobileSettingsPopupMenuItem.help:
_openHelpPage(context);
break;
- case _MobileSettingsPopupMenuItem.helpAndDocumentation:
- _openHelpAndDocumentationPage(context);
- break;
}
},
child: const Padding(
@@ -133,10 +123,6 @@ class HomePageSettingsPopupMenu extends StatelessWidget {
void _openSettingsPage(BuildContext context) {
context.push(MobileHomeSettingPage.routeName);
}
-
- void _openHelpAndDocumentationPage(BuildContext context) {
- afLaunchUrlString('https://appflowy.com/guide');
- }
}
class _PopupButton extends StatelessWidget {
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart
index 485e07a28c..0197f34940 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart
@@ -339,6 +339,7 @@ class _SpaceMenuItemTrailingState extends State {
context.read().add(const SpaceEvent.duplicate());
showToastNotification(
+ context,
message: LocaleKeys.space_success_duplicateSpace.tr(),
);
@@ -373,6 +374,7 @@ class _SpaceMenuItemTrailingState extends State {
.add(SpaceEvent.rename(space: widget.space, name: name));
showToastNotification(
+ context,
message: LocaleKeys.space_success_renameSpace.tr(),
);
},
@@ -422,6 +424,7 @@ class _SpaceMenuItemTrailingState extends State {
);
showToastNotification(
+ context,
message: LocaleKeys.space_success_updateSpace.tr(),
);
@@ -454,6 +457,7 @@ class _SpaceMenuItemTrailingState extends State {
context.read().add(SpaceEvent.delete(widget.space));
showToastNotification(
+ context,
message: LocaleKeys.space_success_deleteSpace.tr(),
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
index 56f5f3e6ab..7ebfeefbbc 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart
@@ -167,7 +167,8 @@ class _MobileSpaceTabState extends State
children: [
MobileHomeSpace(userProfile: widget.userProfile),
// only show ai chat button for cloud user
- if (widget.userProfile.workspaceAuthType == AuthTypePB.Server)
+ if (widget.userProfile.authenticator ==
+ AuthenticatorPB.AppFlowyCloud)
Positioned(
bottom: MediaQuery.of(context).padding.bottom + 16,
left: 20,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart
index d306f48964..ef7f4492a5 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart
@@ -123,7 +123,6 @@ class _CreateWorkspaceButton extends StatelessWidget {
context.read().add(
UserWorkspaceEvent.createWorkspace(
name,
- AuthTypePB.Server,
),
);
},
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart
index f340319254..862c9876f2 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart
@@ -102,7 +102,7 @@ class MobileInlineActionsWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final hasIcon = item.iconBuilder != null;
+ final hasIcon = item.icon != null;
return Container(
height: 36,
decoration: BoxDecoration(
@@ -119,7 +119,7 @@ class MobileInlineActionsWidget extends StatelessWidget {
child: Row(
children: [
if (hasIcon) ...[
- item.iconBuilder!.call(isSelected),
+ item.icon!.call(isSelected),
SizedBox(width: 12),
],
Flexible(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
index 3c6adb8627..170ef46ac2 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart
@@ -332,6 +332,7 @@ class _NotificationNavigationBar extends StatelessWidget {
}
showToastNotification(
+ context,
message: LocaleKeys
.settings_notifications_markAsReadNotifications_allSuccess
.tr(),
@@ -349,6 +350,7 @@ class _NotificationNavigationBar extends StatelessWidget {
}
showToastNotification(
+ context,
message: LocaleKeys.settings_notifications_archiveNotifications_allSuccess
.tr(),
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart
index 33c2eb3905..a8055b8ba2 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart
@@ -50,9 +50,9 @@ class _MobileNotificationsScreenState extends State
orElse: () =>
const Center(child: CircularProgressIndicator.adaptive()),
workspaceFailure: () => const WorkspaceFailedScreen(),
- success: (workspaceLatest, userProfile) =>
+ success: (workspaceSetting, userProfile) =>
_NotificationScreenContent(
- workspaceLatest: workspaceLatest,
+ workspaceSetting: workspaceSetting,
userProfile: userProfile,
controller: controller,
reminderBloc: reminderBloc,
@@ -66,13 +66,13 @@ class _MobileNotificationsScreenState extends State
class _NotificationScreenContent extends StatelessWidget {
const _NotificationScreenContent({
- required this.workspaceLatest,
+ required this.workspaceSetting,
required this.userProfile,
required this.controller,
required this.reminderBloc,
});
- final WorkspaceLatestPB workspaceLatest;
+ final WorkspaceSettingPB workspaceSetting;
final UserProfilePB userProfile;
final TabController controller;
final ReminderBloc reminderBloc;
@@ -84,7 +84,7 @@ class _NotificationScreenContent extends StatelessWidget {
..add(
SidebarSectionsEvent.initial(
userProfile,
- workspaceLatest.workspaceId,
+ workspaceSetting.workspaceId,
),
),
child: BlocBuilder(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart
index e694f9932d..dfa277f2ef 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart
@@ -108,6 +108,7 @@ class NotificationSettingsPopupMenu extends StatelessWidget {
void _onMarkAllAsRead(BuildContext context) {
showToastNotification(
+ context,
message: LocaleKeys
.settings_notifications_markAsReadNotifications_allSuccess
.tr(),
@@ -118,6 +119,7 @@ class NotificationSettingsPopupMenu extends StatelessWidget {
void _onArchiveAll(BuildContext context) {
showToastNotification(
+ context,
message: LocaleKeys.settings_notifications_archiveNotifications_allSuccess
.tr(),
);
@@ -131,6 +133,7 @@ class NotificationSettingsPopupMenu extends StatelessWidget {
}
showToastNotification(
+ context,
message: 'Unarchive all success (Debug Mode)',
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart
index d1216eed98..85f468c76c 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart
@@ -31,6 +31,7 @@ enum NotificationPaneActionType {
size: 24.0,
onPressed: (context) {
showToastNotification(
+ context,
message: LocaleKeys
.settings_notifications_markAsReadNotifications_success
.tr(),
@@ -54,6 +55,7 @@ enum NotificationPaneActionType {
size: 24.0,
onPressed: (context) {
showToastNotification(
+ context,
message: 'Unarchive notification success',
);
@@ -166,6 +168,7 @@ class _NotificationMoreActions extends StatelessWidget {
Navigator.of(context).pop();
showToastNotification(
+ context,
message: LocaleKeys.settings_notifications_markAsReadNotifications_success
.tr(),
);
@@ -188,6 +191,7 @@ class _NotificationMoreActions extends StatelessWidget {
void _onArchive(BuildContext context) {
showToastNotification(
+ context,
message: LocaleKeys.settings_notifications_archiveNotifications_success
.tr()
.tr(),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart
index 45e801e07c..7dda8f0a14 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart
@@ -74,6 +74,7 @@ class _NotificationTabState extends State
if (context.mounted) {
showToastNotification(
+ context,
message: LocaleKeys.settings_notifications_refreshSuccess.tr(),
);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart
index f69360575a..b65c9f9347 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart
@@ -1,4 +1,5 @@
import 'dart:async';
+import 'dart:math';
import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -35,16 +36,12 @@ class MobileSelectionMenu extends SelectionMenuService {
Alignment _alignment = Alignment.topLeft;
final int itemCountFilter;
final int startOffset;
- ValueNotifier<_Position> _positionNotifier = ValueNotifier(_Position.zero);
@override
void dismiss() {
if (_selectionMenuEntry != null) {
editorState.service.keyboardService?.enable();
editorState.service.scrollService?.enable();
- editorState
- .removeScrollViewScrolledListener(_checkPositionAfterScrolling);
- _positionNotifier.dispose();
}
_selectionMenuEntry?.remove();
@@ -56,21 +53,23 @@ class MobileSelectionMenu extends SelectionMenuService {
final completer = Completer();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_show();
- editorState.addScrollViewScrolledListener(_checkPositionAfterScrolling);
completer.complete();
});
return completer.future;
}
void _show() {
- final position = _getCurrentPosition();
- if (position == null) return;
+ final selectionRects = editorState.selectionRects();
+ if (selectionRects.isEmpty) {
+ return;
+ }
+
+ calculateSelectionMenuOffset(selectionRects.first);
+ final (left, top, right, bottom) = getPosition();
final editorHeight = editorState.renderBox!.size.height;
final editorWidth = editorState.renderBox!.size.width;
- _positionNotifier = ValueNotifier(position);
- final showAtTop = position.top != null;
_selectionMenuEntry = OverlayEntry(
builder: (context) {
return SizedBox(
@@ -81,55 +80,47 @@ class MobileSelectionMenu extends SelectionMenuService {
onTap: dismiss,
child: Stack(
children: [
- ValueListenableBuilder(
- valueListenable: _positionNotifier,
- builder: (context, value, _) {
- return Positioned(
- top: value.top,
- bottom: value.bottom,
- left: value.left,
- right: value.right,
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- child: MobileSelectionMenuWidget(
- selectionMenuStyle: style,
- singleColumn: singleColumn,
- showAtTop: showAtTop,
- items: selectionMenuItems
- ..forEach((element) {
- if (element is MobileSelectionMenuItem) {
- element.deleteSlash = false;
- element.deleteKeywords =
- deleteKeywordsByDefault;
- for (final e in element.children) {
- e.deleteSlash = deleteSlashByDefault;
- e.deleteKeywords = deleteKeywordsByDefault;
- e.onSelected = () {
- dismiss();
- };
- }
- } else {
- element.deleteSlash = deleteSlashByDefault;
- element.deleteKeywords =
- deleteKeywordsByDefault;
- element.onSelected = () {
- dismiss();
- };
- }
- }),
- maxItemInRow: 5,
- editorState: editorState,
- itemCountFilter: itemCountFilter,
- startOffset: startOffset,
- menuService: this,
- onExit: () {
- dismiss();
- },
- deleteSlashByDefault: deleteSlashByDefault,
- ),
- ),
- );
- },
+ Positioned(
+ top: top,
+ bottom: bottom,
+ left: left,
+ right: right,
+ child: SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ child: MobileSelectionMenuWidget(
+ selectionMenuStyle: style,
+ singleColumn: singleColumn,
+ items: selectionMenuItems
+ ..forEach((element) {
+ if (element is MobileSelectionMenuItem) {
+ element.deleteSlash = false;
+ element.deleteKeywords = deleteKeywordsByDefault;
+ for (final e in element.children) {
+ e.deleteSlash = deleteSlashByDefault;
+ e.deleteKeywords = deleteKeywordsByDefault;
+ e.onSelected = () {
+ dismiss();
+ };
+ }
+ } else {
+ element.deleteSlash = deleteSlashByDefault;
+ element.deleteKeywords = deleteKeywordsByDefault;
+ element.onSelected = () {
+ dismiss();
+ };
+ }
+ }),
+ maxItemInRow: 5,
+ editorState: editorState,
+ itemCountFilter: itemCountFilter,
+ startOffset: startOffset,
+ menuService: this,
+ onExit: () {
+ dismiss();
+ },
+ deleteSlashByDefault: deleteSlashByDefault,
+ ),
+ ),
),
],
),
@@ -144,35 +135,6 @@ class MobileSelectionMenu extends SelectionMenuService {
editorState.service.scrollService?.disable();
}
- /// the workaround for: editor auto scrolling that will cause wrong position
- /// of slash menu
- void _checkPositionAfterScrolling() {
- final position = _getCurrentPosition();
- if (position == null) return;
- if (position == _positionNotifier.value) {
- Future.delayed(const Duration(milliseconds: 100)).then((_) {
- final position = _getCurrentPosition();
- if (position == null) return;
- if (position != _positionNotifier.value) {
- _positionNotifier.value = position;
- }
- });
- } else {
- _positionNotifier.value = position;
- }
- }
-
- _Position? _getCurrentPosition() {
- final selectionRects = editorState.selectionRects();
- if (selectionRects.isEmpty) {
- return null;
- }
- final screenSize = MediaQuery.of(context).size;
- calculateSelectionMenuOffset(selectionRects.first, screenSize);
- final (left, top, right, bottom) = getPosition();
- return _Position(left, top, right, bottom);
- }
-
@override
Alignment get alignment {
return _alignment;
@@ -204,93 +166,55 @@ class MobileSelectionMenu extends SelectionMenuService {
bottom = offset.dy;
break;
}
+
return (left, top, right, bottom);
}
- void calculateSelectionMenuOffset(Rect rect, Size screenSize) {
+ void calculateSelectionMenuOffset(Rect rect) {
// Workaround: We can customize the padding through the [EditorStyle],
// but the coordinates of overlay are not properly converted currently.
// Just subtract the padding here as a result.
- const menuHeight = 192.0, menuWidth = 240.0;
+ const menuHeight = 192.0, menuWidth = 240.0 + 10;
+ const menuOffset = Offset(0, 10);
final editorOffset =
editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero;
final editorHeight = editorState.renderBox!.size.height;
- final screenHeight = screenSize.height;
final editorWidth = editorState.renderBox!.size.width;
- final rectHeight = rect.height;
// show below default
- _alignment = Alignment.bottomRight;
- final bottomRight = rect.topLeft;
- final offset = bottomRight;
- final limitX = editorWidth + editorOffset.dx - menuWidth,
- limitY = screenHeight -
- editorHeight +
- editorOffset.dy -
- menuHeight -
- rectHeight;
+ _alignment = Alignment.topLeft;
+ final bottomRight = rect.bottomRight;
+ final topRight = rect.topRight;
+ var offset = bottomRight + menuOffset;
_offset = Offset(
- editorWidth - offset.dx - menuWidth,
- screenHeight - offset.dy - menuHeight - rectHeight,
+ offset.dx,
+ offset.dy,
);
+ // show above
if (offset.dy + menuHeight >= editorOffset.dy + editorHeight) {
- /// show above
- if (offset.dy > menuHeight) {
- _offset = Offset(
- _offset.dx,
- offset.dy - menuHeight,
- );
- _alignment = Alignment.topRight;
- } else {
- _offset = Offset(
- _offset.dx,
- limitY,
- );
- }
+ offset = topRight - menuOffset;
+ _alignment = Alignment.bottomLeft;
+
+ final limitX = editorWidth - menuWidth;
+ _offset = Offset(
+ min(offset.dx, limitX),
+ MediaQuery.of(context).size.height - offset.dy,
+ );
}
- if (offset.dx + menuWidth >= editorOffset.dx + editorWidth) {
- /// show left
- if (offset.dx > menuWidth) {
- _alignment = _alignment == Alignment.bottomRight
- ? Alignment.bottomLeft
- : Alignment.topLeft;
- _offset = Offset(
- offset.dx - menuWidth,
- _offset.dy,
- );
- } else {
- _offset = Offset(
- limitX,
- _offset.dy,
- );
- }
+ // show on left
+ if (_offset.dx - editorOffset.dx > editorWidth / 2) {
+ _alignment = _alignment == Alignment.topLeft
+ ? Alignment.topRight
+ : Alignment.bottomRight;
+
+ final x = editorWidth - _offset.dx + editorOffset.dx;
+ final limitX = editorWidth - menuWidth + editorOffset.dx;
+ _offset = Offset(
+ min(x, limitX),
+ _offset.dy,
+ );
}
}
}
-
-class _Position {
- const _Position(this.left, this.top, this.right, this.bottom);
-
- final double? left;
- final double? top;
- final double? right;
- final double? bottom;
-
- static const _Position zero = _Position(0, 0, 0, 0);
-
- @override
- bool operator ==(Object other) =>
- identical(this, other) ||
- other is _Position &&
- runtimeType == other.runtimeType &&
- left == other.left &&
- top == other.top &&
- right == other.right &&
- bottom == other.bottom;
-
- @override
- int get hashCode =>
- left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode;
-}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart
index bdee8f1857..ae5b0b11ac 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart
@@ -36,7 +36,9 @@ class MobileSelectionMenuItemWidget extends StatelessWidget {
),
style: ButtonStyle(
alignment: Alignment.centerLeft,
- overlayColor: WidgetStateProperty.all(Colors.transparent),
+ overlayColor: WidgetStateProperty.all(
+ style.selectionMenuItemSelectedColor,
+ ),
backgroundColor: isSelected
? WidgetStateProperty.all(
style.selectionMenuItemSelectedColor,
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart
index d96dd224e1..ed080e6186 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart
@@ -1,8 +1,6 @@
import 'dart:math';
-import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'mobile_selection_menu_item.dart';
@@ -22,7 +20,6 @@ class MobileSelectionMenuWidget extends StatefulWidget {
required this.deleteSlashByDefault,
required this.singleColumn,
required this.startOffset,
- required this.showAtTop,
this.nameBuilder,
});
@@ -39,7 +36,6 @@ class MobileSelectionMenuWidget extends StatefulWidget {
final bool deleteSlashByDefault;
final bool singleColumn;
- final bool showAtTop;
final int startOffset;
final SelectionMenuItemNameBuilder? nameBuilder;
@@ -174,37 +170,27 @@ class _MobileSelectionMenuWidgetState extends State {
@override
Widget build(BuildContext context) {
- return SizedBox(
- height: 192,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- if (widget.showAtTop) Spacer(),
- Focus(
- focusNode: _focusNode,
- child: DecoratedBox(
- decoration: BoxDecoration(
- color: widget.selectionMenuStyle.selectionMenuBackgroundColor,
- boxShadow: [
- BoxShadow(
- blurRadius: 5,
- spreadRadius: 1,
- color: Colors.black.withValues(alpha: 0.1),
- ),
- ],
- borderRadius: BorderRadius.circular(6.0),
- ),
- child: _showingItems.isEmpty
- ? _buildNoResultsWidget(context)
- : _buildResultsWidget(
- context,
- _showingItems,
- widget.itemCountFilter,
- ),
+ return Focus(
+ focusNode: _focusNode,
+ child: DecoratedBox(
+ decoration: BoxDecoration(
+ color: widget.selectionMenuStyle.selectionMenuBackgroundColor,
+ boxShadow: [
+ BoxShadow(
+ blurRadius: 5,
+ spreadRadius: 1,
+ color: Colors.black.withValues(alpha: 0.1),
),
- ),
- if (!widget.showAtTop) Spacer(),
- ],
+ ],
+ borderRadius: BorderRadius.circular(6.0),
+ ),
+ child: _showingItems.isEmpty
+ ? _buildNoResultsWidget(context)
+ : _buildResultsWidget(
+ context,
+ _showingItems,
+ widget.itemCountFilter,
+ ),
),
);
}
@@ -328,32 +314,15 @@ class _MobileSelectionMenuWidgetState extends State {
}
Widget _buildNoResultsWidget(BuildContext context) {
- return DecoratedBox(
- decoration: BoxDecoration(
- color: Theme.of(context).cardColor,
- boxShadow: [
- BoxShadow(
- blurRadius: 5,
- spreadRadius: 1,
- color: Colors.black.withValues(alpha: 0.1),
- ),
- ],
- borderRadius: BorderRadius.circular(12.0),
- ),
+ return const Padding(
+ padding: EdgeInsets.all(8.0),
child: SizedBox(
- width: 240,
- height: 48,
- child: Padding(
- padding: const EdgeInsets.all(6.0),
- child: Material(
- color: Colors.transparent,
- child: Center(
- child: Text(
- LocaleKeys.inlineActions_noResults.tr(),
- style: TextStyle(fontSize: 18.0, color: Color(0x801F2225)),
- textAlign: TextAlign.center,
- ),
- ),
+ width: 140,
+ child: Material(
+ child: Text(
+ "No results",
+ style: TextStyle(fontSize: 18.0, color: Colors.grey),
+ textAlign: TextAlign.center,
),
),
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart
index 2d5a3176cd..d4f0766626 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart
@@ -25,14 +25,14 @@ class AboutSettingGroup extends StatelessWidget {
trailing: const Icon(
Icons.chevron_right,
),
- onTap: () => afLaunchUrlString('https://appflowy.com/privacy'),
+ onTap: () => afLaunchUrlString('https://appflowy.io/privacy'),
),
MobileSettingItem(
name: LocaleKeys.settings_mobile_termsAndConditions.tr(),
trailing: const Icon(
Icons.chevron_right,
),
- onTap: () => afLaunchUrlString('https://appflowy.com/terms'),
+ onTap: () => afLaunchUrlString('https://appflowy.io/terms'),
),
if (kDebugMode)
MobileSettingItem(
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart
index b43ada6e42..f67cc9e6b8 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart
@@ -5,6 +5,8 @@ import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/workspace.pbenum.dart';
+import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
@@ -16,10 +18,12 @@ class AiSettingsGroup extends StatelessWidget {
super.key,
required this.userProfile,
required this.workspaceId,
+ this.currentWorkspaceMemberRole,
});
final UserProfilePB userProfile;
final String workspaceId;
+ final AFRolePB? currentWorkspaceMemberRole;
@override
Widget build(BuildContext context) {
@@ -28,6 +32,7 @@ class AiSettingsGroup extends StatelessWidget {
create: (context) => SettingsAIBloc(
userProfile,
workspaceId,
+ currentWorkspaceMemberRole,
)..add(const SettingsAIEvent.started()),
child: BlocBuilder(
builder: (context, state) {
@@ -43,7 +48,7 @@ class AiSettingsGroup extends StatelessWidget {
children: [
Flexible(
child: FlowyText(
- state.availableModels?.selectedModel.name ?? "",
+ state.selectedAIModel,
color: theme.colorScheme.onSurface,
overflow: TextOverflow.ellipsis,
),
@@ -79,19 +84,16 @@ class AiSettingsGroup extends StatelessWidget {
title: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
builder: (_) {
return Column(
- children: (availableModels?.models ?? [])
- .asMap()
- .entries
- .map(
- (entry) => FlowyOptionTile.checkbox(
- text: entry.value.name,
- showTopBorder: entry.key == 0,
- isSelected:
- availableModels?.selectedModel.name == entry.value.name,
+ children: availableModels
+ .mapIndexed(
+ (index, model) => FlowyOptionTile.checkbox(
+ text: model,
+ showTopBorder: index == 0,
+ isSelected: state.selectedAIModel == model,
onTap: () {
context
.read()
- .add(SettingsAIEvent.selectModel(entry.value));
+ .add(SettingsAIEvent.selectModel(model));
context.pop();
},
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart
index 02d620e559..24c50f7ae6 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart
@@ -1,7 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@@ -19,7 +18,6 @@ class AppFlowyCloudPage extends StatelessWidget {
),
body: SettingCloud(
restartAppFlowy: () async {
- await getIt().signOut();
await runAppFlowy();
},
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart
index 28ebdb750e..cfdf3defb0 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart
@@ -1,3 +1,5 @@
+import 'package:flutter/material.dart';
+
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
@@ -5,10 +7,10 @@ import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/widgets.dart';
+
import 'personal_info.dart';
class PersonalInfoSettingGroup extends StatelessWidget {
@@ -30,7 +32,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
selector: (state) => state.userProfile.name,
builder: (context, userName) {
return MobileSettingGroup(
- groupTitle: LocaleKeys.settings_accountPage_title.tr(),
+ groupTitle: LocaleKeys.settings_mobile_personalInfo.tr(),
settingItemList: [
MobileSettingItem(
name: userName,
@@ -58,7 +60,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
userName: userName,
onSubmitted: (value) => context
.read()
- .add(SettingsUserEvent.updateUserName(name: value)),
+ .add(SettingsUserEvent.updateUserName(value)),
);
},
);
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
index e5e4efef77..584b867736 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart
@@ -81,6 +81,7 @@ class SupportSettingGroup extends StatelessWidget {
);
if (context.mounted) {
showToastNotification(
+ context,
message: LocaleKeys.settings_files_clearCacheSuccess.tr(),
);
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
index 5ca5525099..b3b7cb71c5 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart
@@ -40,7 +40,7 @@ class UserSessionSettingGroup extends StatelessWidget {
// delete account button
// only show the delete account button in cloud mode
- if (userProfile.workspaceAuthType == AuthTypePB.Server) ...[
+ if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[
const VSpace(16.0),
MobileLogoutButton(
text: LocaleKeys.button_deleteAccount.tr(),
@@ -63,15 +63,8 @@ class UserSessionSettingGroup extends StatelessWidget {
);
},
builder: (context, state) {
- return Column(
- children: [
- const ContinueWithEmailAndPassword(),
- const VSpace(12.0),
- const ThirdPartySignInButtons(
- expanded: true,
- ),
- const VSpace(16.0),
- ],
+ return const ThirdPartySignInButtons(
+ expanded: true,
);
},
),
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart
index 18bce0588b..2e805c5c5a 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart
@@ -197,10 +197,11 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
// only show the result dialog when the action is WorkspaceMemberActionType.add
- if (actionType == WorkspaceMemberActionType.addByEmail) {
+ if (actionType == WorkspaceMemberActionType.add) {
result.fold(
(s) {
showToastNotification(
+ context,
message:
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
bottomPadding: keyboardHeight,
@@ -217,16 +218,18 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
});
showToastNotification(
+ context,
type: ToastificationType.error,
bottomPadding: keyboardHeight,
message: message,
);
},
);
- } else if (actionType == WorkspaceMemberActionType.inviteByEmail) {
+ } else if (actionType == WorkspaceMemberActionType.invite) {
result.fold(
(s) {
showToastNotification(
+ context,
message:
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
bottomPadding: keyboardHeight,
@@ -244,16 +247,18 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
});
showToastNotification(
+ context,
type: ToastificationType.error,
message: message,
bottomPadding: keyboardHeight,
);
},
);
- } else if (actionType == WorkspaceMemberActionType.removeByEmail) {
+ } else if (actionType == WorkspaceMemberActionType.remove) {
result.fold(
(s) {
showToastNotification(
+ context,
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceSuccess
.tr(),
@@ -262,6 +267,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
},
(f) {
showToastNotification(
+ context,
type: ToastificationType.error,
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceFailed
@@ -276,15 +282,15 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
void _inviteMember(BuildContext context) {
final email = emailController.text;
if (!isEmail(email)) {
- showToastNotification(
+ return showToastNotification(
+ context,
type: ToastificationType.error,
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
);
- return;
}
context
.read()
- .add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email));
+ .add(WorkspaceMemberEvent.inviteWorkspaceMember(email));
// clear the email field after inviting
emailController.clear();
}
diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart
index b2805d5857..501fd18ef7 100644
--- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart
+++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart
@@ -178,7 +178,7 @@ class _MemberItem extends StatelessWidget {
showBottomBorder: false,
onTap: () {
workspaceMemberBloc.add(
- WorkspaceMemberEvent.removeWorkspaceMemberByEmail(
+ WorkspaceMemberEvent.removeWorkspaceMember(
member.email,
),
);
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart
deleted file mode 100644
index 2cfc349bf8..0000000000
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart
+++ /dev/null
@@ -1,53 +0,0 @@
-import 'dart:async';
-import 'dart:typed_data';
-
-import 'package:appflowy/plugins/ai_chat/application/chat_notification.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/notification.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart';
-import 'package:appflowy_backend/rust_stream.dart';
-import 'package:appflowy_result/appflowy_result.dart';
-
-typedef OnUpdateSelectedModel = void Function(AIModelPB model);
-
-class AIModelSwitchListener {
- AIModelSwitchListener({required this.objectId}) {
- _parser = ChatNotificationParser(id: objectId, callback: _callback);
- _subscription = RustStreamReceiver.listen(
- (observable) => _parser?.parse(observable),
- );
- }
-
- final String objectId;
- StreamSubscription? _subscription;
- ChatNotificationParser? _parser;
-
- void start({
- OnUpdateSelectedModel? onUpdateSelectedModel,
- }) {
- this.onUpdateSelectedModel = onUpdateSelectedModel;
- }
-
- OnUpdateSelectedModel? onUpdateSelectedModel;
-
- void _callback(
- ChatNotification ty,
- FlowyResult result,
- ) {
- result.map((r) {
- switch (ty) {
- case ChatNotification.DidUpdateSelectedModel:
- onUpdateSelectedModel?.call(AIModelPB.fromBuffer(r));
- break;
- default:
- break;
- }
- });
- }
-
- Future stop() async {
- await _subscription?.cancel();
- _subscription = null;
- }
-}
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart
index 47c1668a2c..60fca000c0 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart
@@ -23,126 +23,11 @@ class ChatAIMessageBloc extends Bloc {
parseMetadata(refSourceJsonString),
),
) {
- _registerEventHandlers();
- _initializeStreamListener();
- _checkInitialStreamState();
- }
+ _dispatch();
- final String chatId;
- final Int64? questionId;
-
- void _registerEventHandlers() {
- on<_UpdateText>((event, emit) {
- emit(
- state.copyWith(
- text: event.text,
- messageState: const MessageState.ready(),
- ),
- );
- });
-
- on<_ReceiveError>((event, emit) {
- emit(state.copyWith(messageState: MessageState.onError(event.error)));
- });
-
- on<_Retry>((event, emit) async {
- if (questionId == null) {
- Log.error("Question id is not valid: $questionId");
- return;
- }
- emit(state.copyWith(messageState: const MessageState.loading()));
- final payload = ChatMessageIdPB(
- chatId: chatId,
- messageId: questionId,
- );
- final result = await AIEventGetAnswerForQuestion(payload).send();
- if (!isClosed) {
- result.fold(
- (answer) => add(ChatAIMessageEvent.retryResult(answer.content)),
- (err) {
- Log.error("Failed to get answer: $err");
- add(ChatAIMessageEvent.receiveError(err.toString()));
- },
- );
- }
- });
-
- on<_RetryResult>((event, emit) {
- emit(
- state.copyWith(
- text: event.text,
- messageState: const MessageState.ready(),
- ),
- );
- });
-
- on<_OnAIResponseLimit>((event, emit) {
- emit(
- state.copyWith(
- messageState: const MessageState.onAIResponseLimit(),
- ),
- );
- });
-
- on<_OnAIImageResponseLimit>((event, emit) {
- emit(
- state.copyWith(
- messageState: const MessageState.onAIImageResponseLimit(),
- ),
- );
- });
-
- on<_OnAIMaxRquired>((event, emit) {
- emit(
- state.copyWith(
- messageState: MessageState.onAIMaxRequired(event.message),
- ),
- );
- });
-
- on<_OnLocalAIInitializing>((event, emit) {
- emit(
- state.copyWith(
- messageState: const MessageState.onInitializingLocalAI(),
- ),
- );
- });
-
- on<_ReceiveMetadata>((event, emit) {
- Log.debug("AI Steps: ${event.metadata.progress?.step}");
- emit(
- state.copyWith(
- sources: event.metadata.sources,
- progress: event.metadata.progress,
- ),
- );
- });
- }
-
- void _initializeStreamListener() {
if (state.stream != null) {
- state.stream!.listen(
- onData: (text) => _safeAdd(ChatAIMessageEvent.updateText(text)),
- onError: (error) =>
- _safeAdd(ChatAIMessageEvent.receiveError(error.toString())),
- onAIResponseLimit: () =>
- _safeAdd(const ChatAIMessageEvent.onAIResponseLimit()),
- onAIImageResponseLimit: () =>
- _safeAdd(const ChatAIMessageEvent.onAIImageResponseLimit()),
- onMetadata: (metadata) =>
- _safeAdd(ChatAIMessageEvent.receiveMetadata(metadata)),
- onAIMaxRequired: (message) {
- Log.info(message);
- _safeAdd(ChatAIMessageEvent.onAIMaxRequired(message));
- },
- onLocalAIInitializing: () =>
- _safeAdd(const ChatAIMessageEvent.onLocalAIInitializing()),
- );
- }
- }
+ _startListening();
- void _checkInitialStreamState() {
- if (state.stream != null) {
if (state.stream!.aiLimitReached) {
add(const ChatAIMessageEvent.onAIResponseLimit());
} else if (state.stream!.error != null) {
@@ -151,10 +36,130 @@ class ChatAIMessageBloc extends Bloc {
}
}
- void _safeAdd(ChatAIMessageEvent event) {
- if (!isClosed) {
- add(event);
- }
+ final String chatId;
+ final Int64? questionId;
+
+ void _dispatch() {
+ on(
+ (event, emit) {
+ event.when(
+ updateText: (newText) {
+ emit(
+ state.copyWith(
+ text: newText,
+ messageState: const MessageState.ready(),
+ ),
+ );
+ },
+ receiveError: (error) {
+ emit(state.copyWith(messageState: MessageState.onError(error)));
+ },
+ retry: () {
+ if (questionId is! Int64) {
+ Log.error("Question id is not Int64: $questionId");
+ return;
+ }
+ emit(
+ state.copyWith(
+ messageState: const MessageState.loading(),
+ ),
+ );
+
+ final payload = ChatMessageIdPB(
+ chatId: chatId,
+ messageId: questionId,
+ );
+ AIEventGetAnswerForQuestion(payload).send().then((result) {
+ if (!isClosed) {
+ result.fold(
+ (answer) {
+ add(ChatAIMessageEvent.retryResult(answer.content));
+ },
+ (err) {
+ Log.error("Failed to get answer: $err");
+ add(ChatAIMessageEvent.receiveError(err.toString()));
+ },
+ );
+ }
+ });
+ },
+ retryResult: (String text) {
+ emit(
+ state.copyWith(
+ text: text,
+ messageState: const MessageState.ready(),
+ ),
+ );
+ },
+ onAIResponseLimit: () {
+ emit(
+ state.copyWith(
+ messageState: const MessageState.onAIResponseLimit(),
+ ),
+ );
+ },
+ onAIImageResponseLimit: () {
+ emit(
+ state.copyWith(
+ messageState: const MessageState.onAIImageResponseLimit(),
+ ),
+ );
+ },
+ onAIMaxRequired: (message) {
+ emit(
+ state.copyWith(
+ messageState: MessageState.onAIMaxRequired(message),
+ ),
+ );
+ },
+ receiveMetadata: (metadata) {
+ Log.debug("AI Steps: ${metadata.progress?.step}");
+ emit(
+ state.copyWith(
+ sources: metadata.sources,
+ progress: metadata.progress,
+ ),
+ );
+ },
+ );
+ },
+ );
+ }
+
+ void _startListening() {
+ state.stream!.listen(
+ onData: (text) {
+ if (!isClosed) {
+ add(ChatAIMessageEvent.updateText(text));
+ }
+ },
+ onError: (error) {
+ if (!isClosed) {
+ add(ChatAIMessageEvent.receiveError(error.toString()));
+ }
+ },
+ onAIResponseLimit: () {
+ if (!isClosed) {
+ add(const ChatAIMessageEvent.onAIResponseLimit());
+ }
+ },
+ onAIImageResponseLimit: () {
+ if (!isClosed) {
+ add(const ChatAIMessageEvent.onAIImageResponseLimit());
+ }
+ },
+ onMetadata: (metadata) {
+ if (!isClosed) {
+ add(ChatAIMessageEvent.receiveMetadata(metadata));
+ }
+ },
+ onAIMaxRequired: (message) {
+ if (!isClosed) {
+ Log.info(message);
+ add(ChatAIMessageEvent.onAIMaxRequired(message));
+ }
+ },
+ );
}
}
@@ -169,8 +174,6 @@ class ChatAIMessageEvent with _$ChatAIMessageEvent {
_OnAIImageResponseLimit;
const factory ChatAIMessageEvent.onAIMaxRequired(String message) =
_OnAIMaxRquired;
- const factory ChatAIMessageEvent.onLocalAIInitializing() =
- _OnLocalAIInitializing;
const factory ChatAIMessageEvent.receiveMetadata(
MetadataCollection metadata,
) = _ReceiveMetadata;
@@ -206,7 +209,6 @@ class MessageState with _$MessageState {
const factory MessageState.onAIResponseLimit() = _AIResponseLimit;
const factory MessageState.onAIImageResponseLimit() = _AIImageResponseLimit;
const factory MessageState.onAIMaxRequired(String message) = _AIMaxRequired;
- const factory MessageState.onInitializingLocalAI() = _LocalAIInitializing;
const factory MessageState.ready() = _Ready;
const factory MessageState.loading() = _Loading;
}
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart
index 602b46f97a..e7aca346e0 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart
@@ -239,9 +239,9 @@ class ChatBloc extends Bloc {
),
);
},
- regenerateAnswer: (id, format, model) {
+ regenerateAnswer: (id, format) {
_clearRelatedQuestions();
- _regenerateAnswer(id, format, model);
+ _regenerateAnswer(id, format);
lastSentMessage = null;
isFetchingRelatedQuestions = false;
@@ -435,7 +435,7 @@ class ChatBloc extends Bloc {
messageType: ChatMessageTypePB.User,
questionStreamPort: Int64(questionStream.nativePort),
answerStreamPort: Int64(answerStream!.nativePort),
- //metadata: await metadataPBFromMetadata(metadata),
+ metadata: await metadataPBFromMetadata(metadata),
);
if (format != null) {
payload.format = format.toPB();
@@ -483,7 +483,6 @@ class ChatBloc extends Bloc {
void _regenerateAnswer(
String answerMessageIdString,
PredefinedFormat? format,
- AIModelPB? model,
) async {
final id = temporaryMessageIDMap.entries
.firstWhereOrNull((e) => e.value == answerMessageIdString)
@@ -506,9 +505,6 @@ class ChatBloc extends Bloc {
if (format != null) {
payload.format = format.toPB();
}
- if (model != null) {
- payload.model = model;
- }
await AIEventRegenerateResponse(payload).send().fold(
(success) {
@@ -641,7 +637,6 @@ class ChatEvent with _$ChatEvent {
const factory ChatEvent.regenerateAnswer(
String id,
PredefinedFormat? format,
- AIModelPB? model,
) = _RegenerateAnswer;
// streaming answer
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart
index 2547ff668e..8718255cd9 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart
@@ -27,7 +27,6 @@ class ChatMemberBloc extends Bloc {
final payload = WorkspaceMemberIdPB(
uid: Int64.parseInt(userId),
);
-
await UserEventGetMemberInfo(payload).send().then((result) {
result.fold(
(member) {
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart
index c22559f21b..df6c1993a1 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart
@@ -2,25 +2,55 @@ import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
-import 'package:appflowy/ai/service/ai_entities.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_message_service.dart';
-/// A stream that receives answer events from an isolate or external process.
-/// It caches events that might occur before a listener is attached.
class AnswerStream {
AnswerStream() {
_port.handler = _controller.add;
_subscription = _controller.stream.listen(
- _handleEvent,
- onDone: _onDoneCallback,
- onError: _handleError,
+ (event) {
+ if (event.startsWith("data:")) {
+ _hasStarted = true;
+ final newText = event.substring(5);
+ _text += newText;
+ _onData?.call(_text);
+ } else if (event.startsWith("error:")) {
+ _error = event.substring(5);
+ _onError?.call(_error!);
+ } else if (event.startsWith("metadata:")) {
+ if (_onMetadata != null) {
+ final s = event.substring(9);
+ _onMetadata!(parseMetadata(s));
+ }
+ } else if (event == "AI_RESPONSE_LIMIT") {
+ _aiLimitReached = true;
+ _onAIResponseLimit?.call();
+ } else if (event == "AI_IMAGE_RESPONSE_LIMIT") {
+ _aiImageLimitReached = true;
+ _onAIImageResponseLimit?.call();
+ } else if (event.startsWith("AI_MAX_REQUIRED:")) {
+ final msg = event.substring(16);
+ // If the callback is not registered yet, add the event to the buffer.
+ if (_onAIMaxRequired != null) {
+ _onAIMaxRequired!(msg);
+ } else {
+ _pendingAIMaxRequiredEvents.add(msg);
+ }
+ }
+ },
+ onDone: () {
+ _onEnd?.call();
+ },
+ onError: (error) {
+ _error = error.toString();
+ _onError?.call(error.toString());
+ },
);
}
final RawReceivePort _port = RawReceivePort();
final StreamController _controller = StreamController.broadcast();
late StreamSubscription _subscription;
-
bool _hasStarted = false;
bool _aiLimitReached = false;
bool _aiImageLimitReached = false;
@@ -32,15 +62,13 @@ class AnswerStream {
void Function()? _onStart;
void Function()? _onEnd;
void Function(String error)? _onError;
- void Function()? _onLocalAIInitializing;
void Function()? _onAIResponseLimit;
void Function()? _onAIImageResponseLimit;
void Function(String message)? _onAIMaxRequired;
- void Function(MetadataCollection metadata)? _onMetadata;
+ void Function(MetadataCollection metadataCollection)? _onMetadata;
- // Caches for events that occur before listen() is called.
+ // Buffer for events that occur before listen() is called.
final List _pendingAIMaxRequiredEvents = [];
- bool _pendingLocalAINotReady = false;
int get nativePort => _port.sendPort.nativePort;
bool get hasStarted => _hasStarted;
@@ -49,61 +77,12 @@ class AnswerStream {
String? get error => _error;
String get text => _text;
- /// Releases the resources used by the AnswerStream.
Future dispose() async {
await _controller.close();
await _subscription.cancel();
_port.close();
}
- /// Handles incoming events from the underlying stream.
- void _handleEvent(String event) {
- if (event.startsWith(AIStreamEventPrefix.data)) {
- _hasStarted = true;
- final newText = event.substring(AIStreamEventPrefix.data.length);
- _text += newText;
- _onData?.call(_text);
- } else if (event.startsWith(AIStreamEventPrefix.error)) {
- _error = event.substring(AIStreamEventPrefix.error.length);
- _onError?.call(_error!);
- } else if (event.startsWith(AIStreamEventPrefix.metadata)) {
- final s = event.substring(AIStreamEventPrefix.metadata.length);
- _onMetadata?.call(parseMetadata(s));
- } else if (event == AIStreamEventPrefix.aiResponseLimit) {
- _aiLimitReached = true;
- _onAIResponseLimit?.call();
- } else if (event == AIStreamEventPrefix.aiImageResponseLimit) {
- _aiImageLimitReached = true;
- _onAIImageResponseLimit?.call();
- } else if (event.startsWith(AIStreamEventPrefix.aiMaxRequired)) {
- final msg = event.substring(AIStreamEventPrefix.aiMaxRequired.length);
- if (_onAIMaxRequired != null) {
- _onAIMaxRequired!(msg);
- } else {
- _pendingAIMaxRequiredEvents.add(msg);
- }
- } else if (event.startsWith(AIStreamEventPrefix.localAINotReady)) {
- if (_onLocalAIInitializing != null) {
- _onLocalAIInitializing!();
- } else {
- _pendingLocalAINotReady = true;
- }
- }
- }
-
- void _onDoneCallback() {
- _onEnd?.call();
- }
-
- void _handleError(dynamic error) {
- _error = error.toString();
- _onError?.call(_error!);
- }
-
- /// Registers listeners for various events.
- ///
- /// If certain events have already occurred (e.g. AI_MAX_REQUIRED or LOCAL_AI_NOT_READY),
- /// they will be flushed immediately.
void listen({
void Function(String text)? onData,
void Function()? onStart,
@@ -113,7 +92,6 @@ class AnswerStream {
void Function()? onAIImageResponseLimit,
void Function(String message)? onAIMaxRequired,
void Function(MetadataCollection metadata)? onMetadata,
- void Function()? onLocalAIInitializing,
}) {
_onData = onData;
_onStart = onStart;
@@ -121,11 +99,10 @@ class AnswerStream {
_onError = onError;
_onAIResponseLimit = onAIResponseLimit;
_onAIImageResponseLimit = onAIImageResponseLimit;
- _onAIMaxRequired = onAIMaxRequired;
_onMetadata = onMetadata;
- _onLocalAIInitializing = onLocalAIInitializing;
+ _onAIMaxRequired = onAIMaxRequired;
- // Flush pending AI_MAX_REQUIRED events.
+ // Flush any buffered AI_MAX_REQUIRED events.
if (_onAIMaxRequired != null && _pendingAIMaxRequiredEvents.isNotEmpty) {
for (final msg in _pendingAIMaxRequiredEvents) {
_onAIMaxRequired!(msg);
@@ -133,12 +110,6 @@ class AnswerStream {
_pendingAIMaxRequiredEvents.clear();
}
- // Flush pending LOCAL_AI_NOT_READY event.
- if (_pendingLocalAINotReady && _onLocalAIInitializing != null) {
- _onLocalAIInitializing!();
- _pendingLocalAINotReady = false;
- }
-
_onStart?.call();
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart
index 9977d1df72..90a2db168f 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart
@@ -19,17 +19,9 @@ class ChatSelectMessageBloc
on(
(event, emit) {
event.when(
- enableStartSelectingMessages: () {
- emit(state.copyWith(enabled: true));
- },
toggleSelectingMessages: () {
if (state.isSelectingMessages) {
- emit(
- state.copyWith(
- isSelectingMessages: false,
- selectedMessages: [],
- ),
- );
+ emit(ChatSelectMessageState.initial());
} else {
emit(state.copyWith(isSelectingMessages: true));
}
@@ -58,13 +50,8 @@ class ChatSelectMessageBloc
unselectAllMessages: () {
emit(state.copyWith(selectedMessages: const []));
},
- reset: () {
- emit(
- state.copyWith(
- isSelectingMessages: false,
- selectedMessages: [],
- ),
- );
+ saveAsPage: () {
+ emit(ChatSelectMessageState.initial());
},
);
},
@@ -83,8 +70,6 @@ class ChatSelectMessageBloc
@freezed
class ChatSelectMessageEvent with _$ChatSelectMessageEvent {
- const factory ChatSelectMessageEvent.enableStartSelectingMessages() =
- _EnableStartSelectingMessages;
const factory ChatSelectMessageEvent.toggleSelectingMessages() =
_ToggleSelectingMessages;
const factory ChatSelectMessageEvent.toggleSelectMessage(Message message) =
@@ -94,7 +79,7 @@ class ChatSelectMessageEvent with _$ChatSelectMessageEvent {
) = _SelectAllMessages;
const factory ChatSelectMessageEvent.unselectAllMessages() =
_UnselectAllMessages;
- const factory ChatSelectMessageEvent.reset() = _Reset;
+ const factory ChatSelectMessageEvent.saveAsPage() = _SaveAsPage;
}
@freezed
@@ -102,11 +87,9 @@ class ChatSelectMessageState with _$ChatSelectMessageState {
const factory ChatSelectMessageState({
required bool isSelectingMessages,
required List selectedMessages,
- required bool enabled,
}) = _ChatSelectMessageState;
factory ChatSelectMessageState.initial() => const ChatSelectMessageState(
- enabled: false,
isSelectingMessages: false,
selectedMessages: [],
);
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart
index 76aba27dc0..1b3880e01d 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart
@@ -178,12 +178,8 @@ class AIChatPagePluginWidgetBuilder extends PluginWidgetBuilder
customActions: [
CustomViewAction(
view: notifier.view,
- disabled: !state.enabled,
leftIcon: FlowySvgs.ai_add_to_page_s,
label: LocaleKeys.moreAction_saveAsNewPage.tr(),
- tooltipMessage: state.enabled
- ? null
- : LocaleKeys.moreAction_saveAsNewPageDisabled.tr(),
onTap: () {
chatMessageSelectorBloc.add(
const ChatSelectMessageEvent
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
index 90085354db..f7f22a3c93 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:appflowy/ai/ai.dart';
+import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/ai_chat/presentation/chat_message_selector_banner.dart';
import 'package:appflowy/workspace/application/view/view_service.dart';
import 'package:appflowy_backend/log.dart';
@@ -8,8 +9,9 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:desktop_drop/desktop_drop.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_chat_core/flutter_chat_core.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart'
@@ -48,14 +50,14 @@ class AIChatPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
- // if (userProfile.authenticator != AuthTypePB.Server) {
- // return Center(
- // child: FlowyText(
- // LocaleKeys.chat_unsupportedCloudPrompt.tr(),
- // fontSize: 20,
- // ),
- // );
- // }
+ if (userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) {
+ return Center(
+ child: FlowyText(
+ LocaleKeys.chat_unsupportedCloudPrompt.tr(),
+ fontSize: 20,
+ ),
+ );
+ }
return MultiBlocProvider(
providers: [
@@ -70,7 +72,6 @@ class AIChatPage extends StatelessWidget {
/// [AIPromptInputBloc] is used to handle the user prompt
BlocProvider(
create: (_) => AIPromptInputBloc(
- objectId: view.id,
predefinedFormat: PredefinedFormat(
imageFormat: ImageFormat.text,
textFormat: TextFormat.bulletList,
@@ -91,29 +92,9 @@ class AIChatPage extends StatelessWidget {
}
}
},
- child: FocusScope(
- onKeyEvent: (focusNode, event) {
- if (event is! KeyUpEvent) {
- return KeyEventResult.ignored;
- }
-
- if (event.logicalKey == LogicalKeyboardKey.escape ||
- event.logicalKey == LogicalKeyboardKey.keyC &&
- HardwareKeyboard.instance.isControlPressed) {
- final chatBloc = context.read();
- if (chatBloc.state.promptResponseState !=
- PromptResponseState.ready) {
- chatBloc.add(ChatEvent.stopStream());
- return KeyEventResult.handled;
- }
- }
-
- return KeyEventResult.ignored;
- },
- child: _ChatContentPage(
- view: view,
- userProfile: userProfile,
- ),
+ child: _ChatContentPage(
+ view: view,
+ userProfile: userProfile,
),
);
},
@@ -262,16 +243,10 @@ class _ChatContentPage extends StatelessWidget {
_onSelectMetadata(context, metadata),
onRegenerate: () => context
.read()
- .add(ChatEvent.regenerateAnswer(message.id, null, null)),
+ .add(ChatEvent.regenerateAnswer(message.id, null)),
onChangeFormat: (format) => context
.read()
- .add(ChatEvent.regenerateAnswer(message.id, format, null)),
- onChangeModel: (model) => context
- .read()
- .add(ChatEvent.regenerateAnswer(message.id, null, model)),
- onStopStream: () => context.read().add(
- const ChatEvent.stopStream(),
- ),
+ .add(ChatEvent.regenerateAnswer(message.id, format)),
);
},
);
@@ -330,10 +305,6 @@ class _ChatContentPage extends StatelessWidget {
);
}
- context
- .read()
- .add(ChatSelectMessageEvent.enableStartSelectingMessages());
-
return BlocSelector(
selector: (state) => state.isSelectingMessages,
builder: (context, isSelectingMessages) {
@@ -355,56 +326,36 @@ class _ChatContentPage extends StatelessWidget {
BuildContext context,
ChatMessageRefSource metadata,
) async {
- // When the source of metatdata is appflowy, which means it is a appflowy page
- if (metadata.source == "appflowy") {
+ if (isURL(metadata.name)) {
+ late Uri uri;
+ try {
+ uri = Uri.parse(metadata.name);
+ // `Uri` identifies `localhost` as a scheme
+ if (!uri.hasScheme || uri.scheme == 'localhost') {
+ uri = Uri.parse("http://${metadata.name}");
+ await InternetAddress.lookup(uri.host);
+ }
+ await launchUrl(uri);
+ } catch (err) {
+ Log.error("failed to open url $err");
+ }
+ } else {
final sidebarView =
await ViewBackendService.getView(metadata.id).toNullable();
if (context.mounted) {
openPageFromMessage(context, sidebarView);
}
- return;
- }
-
- if (metadata.source == "web") {
- if (isURL(metadata.name)) {
- late Uri uri;
- try {
- uri = Uri.parse(metadata.name);
- // `Uri` identifies `localhost` as a scheme
- if (!uri.hasScheme || uri.scheme == 'localhost') {
- uri = Uri.parse("http://${metadata.name}");
- await InternetAddress.lookup(uri.host);
- }
- await launchUrl(uri);
- } catch (err) {
- Log.error("failed to open url $err");
- }
- }
- return;
}
}
}
-class _Input extends StatefulWidget {
+class _Input extends StatelessWidget {
const _Input({
required this.view,
});
final ViewPB view;
- @override
- State<_Input> createState() => _InputState();
-}
-
-class _InputState extends State<_Input> {
- final textController = TextEditingController();
-
- @override
- void dispose() {
- textController.dispose();
- super.dispose();
- }
-
@override
Widget build(BuildContext context) {
return BlocSelector(
@@ -434,7 +385,6 @@ class _InputState extends State<_Input> {
return UniversalPlatform.isDesktop
? DesktopPromptInput(
isStreaming: !canSendMessage,
- textController: textController,
onStopStreaming: () {
chatBloc.add(const ChatEvent.stopStream());
},
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart
index 59b7fbd39b..d5ecd09c38 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart
@@ -27,7 +27,7 @@ class ChatAIAvatar extends StatelessWidget {
child: const CircleAvatar(
backgroundColor: Colors.transparent,
child: FlowySvg(
- FlowySvgs.app_logo_s,
+ FlowySvgs.flowy_logo_s,
size: Size.square(16),
blendMode: null,
),
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart
index 76d1af7134..4d5cd82098 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart
@@ -41,7 +41,7 @@ class _MobileChatInputState extends State {
void initState() {
super.initState();
- textController.addListener(handleTextControllerChanged);
+ textController.addListener(handleTextControllerChange);
// focusNode.onKeyEvent = handleKeyEvent;
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -197,7 +197,7 @@ class _MobileChatInputState extends State {
);
}
- void handleTextControllerChanged() {
+ void handleTextControllerChange() {
if (textController.value.isComposingRangeValid) {
return;
}
@@ -268,8 +268,8 @@ class _MobileChatInputState extends State {
focusedBorder: InputBorder.none,
contentPadding: MobileAIPromptSizes.textFieldContentPadding,
hintText: switch (state.aiType) {
- AiType.cloud => LocaleKeys.chat_inputMessageHint.tr(),
- AiType.local => LocaleKeys.chat_inputLocalAIMessageHint.tr()
+ AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(),
+ AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr()
},
hintStyle: inputHintTextStyle(context),
isCollapsed: true,
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart
index 790a3fac3c..4b25297d63 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart
@@ -250,7 +250,7 @@ class _SaveToPageButtonState extends State {
showSaveMessageSuccessToast(context, view);
}
- bloc.add(const ChatSelectMessageEvent.reset());
+ bloc.add(const ChatSelectMessageEvent.saveAsPage());
return view;
}
@@ -275,7 +275,7 @@ class _SaveToPageButtonState extends State {
showSaveMessageSuccessToast(context, newView);
openPageFromMessage(context, newView);
}
- bloc.add(const ChatSelectMessageEvent.reset());
+ bloc.add(const ChatSelectMessageEvent.saveAsPage());
}
Future forceReload(String documentId) async {
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart
index 2c09e77050..9e4cc603b0 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart
@@ -21,35 +21,33 @@ class RelatedQuestionList extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return SelectionContainer.disabled(
- child: ListView.separated(
- shrinkWrap: true,
- physics: const NeverScrollableScrollPhysics(),
- itemCount: relatedQuestions.length + 1,
- padding:
- const EdgeInsets.only(bottom: 8.0) + AIChatUILayout.messageMargin,
- separatorBuilder: (context, index) => const VSpace(4.0),
- itemBuilder: (context, index) {
- if (index == 0) {
- return Padding(
- padding: const EdgeInsets.only(bottom: 4.0),
- child: FlowyText(
- LocaleKeys.chat_relatedQuestion.tr(),
- color: Theme.of(context).hintColor,
- fontWeight: FontWeight.w600,
- ),
- );
- } else {
- return Align(
- alignment: AlignmentDirectional.centerStart,
- child: RelatedQuestionItem(
- question: relatedQuestions[index - 1],
- onQuestionSelected: onQuestionSelected,
- ),
- );
- }
- },
- ),
+ return ListView.separated(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: relatedQuestions.length + 1,
+ padding:
+ const EdgeInsets.only(bottom: 8.0) + AIChatUILayout.messageMargin,
+ separatorBuilder: (context, index) => const VSpace(4.0),
+ itemBuilder: (context, index) {
+ if (index == 0) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 4.0),
+ child: FlowyText(
+ LocaleKeys.chat_relatedQuestion.tr(),
+ color: Theme.of(context).hintColor,
+ fontWeight: FontWeight.w600,
+ ),
+ );
+ } else {
+ return Align(
+ alignment: AlignmentDirectional.centerStart,
+ child: RelatedQuestionItem(
+ question: relatedQuestions[index - 1],
+ onQuestionSelected: onQuestionSelected,
+ ),
+ );
+ }
+ },
);
}
}
@@ -72,8 +70,7 @@ class RelatedQuestionItem extends StatelessWidget {
child: FlowyText(
question,
lineHeight: 1.4,
- maxLines: 2,
- overflow: TextOverflow.ellipsis,
+ maxLines: null,
),
),
expandText: false,
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
index 30dc918f70..d7a90bd18a 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart
@@ -46,7 +46,7 @@ class ChatWelcomePage extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
const FlowySvg(
- FlowySvgs.app_logo_xl,
+ FlowySvgs.flowy_logo_xl,
size: Size.square(32),
blendMode: null,
),
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart
deleted file mode 100644
index aa0d840574..0000000000
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart
+++ /dev/null
@@ -1,145 +0,0 @@
-import 'package:appflowy/generated/locale_keys.g.dart';
-import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
-import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
-import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
-import 'package:collection/collection.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flutter/material.dart';
-
-Future showChangeModelBottomSheet(
- BuildContext context,
- List models,
-) {
- return showMobileBottomSheet(
- context,
- showDragHandle: true,
- builder: (context) => _ChangeModelBottomSheetContent(models: models),
- );
-}
-
-class _ChangeModelBottomSheetContent extends StatefulWidget {
- const _ChangeModelBottomSheetContent({
- required this.models,
- });
-
- final List models;
-
- @override
- State<_ChangeModelBottomSheetContent> createState() =>
- _ChangeModelBottomSheetContentState();
-}
-
-class _ChangeModelBottomSheetContentState
- extends State<_ChangeModelBottomSheetContent> {
- AIModelPB? model;
-
- @override
- Widget build(BuildContext context) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- _Header(
- onCancel: () => Navigator.of(context).pop(),
- onDone: () => Navigator.of(context).pop(model),
- ),
- const VSpace(4.0),
- _Body(
- models: widget.models,
- selectedModel: model,
- onSelectModel: (format) {
- setState(() => model = format);
- },
- ),
- const VSpace(16.0),
- ],
- );
- }
-}
-
-class _Header extends StatelessWidget {
- const _Header({
- required this.onCancel,
- required this.onDone,
- });
-
- final VoidCallback onCancel;
- final VoidCallback onDone;
-
- @override
- Widget build(BuildContext context) {
- return SizedBox(
- height: 44.0,
- child: Stack(
- children: [
- Align(
- alignment: Alignment.centerLeft,
- child: AppBarBackButton(
- padding: const EdgeInsets.symmetric(
- vertical: 12,
- horizontal: 16,
- ),
- onTap: onCancel,
- ),
- ),
- Align(
- child: Container(
- constraints: const BoxConstraints(maxWidth: 250),
- child: FlowyText(
- LocaleKeys.chat_switchModel_label.tr(),
- fontSize: 17.0,
- fontWeight: FontWeight.w500,
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ),
- Align(
- alignment: Alignment.centerRight,
- child: AppBarDoneButton(
- onTap: onDone,
- ),
- ),
- ],
- ),
- );
- }
-}
-
-class _Body extends StatelessWidget {
- const _Body({
- required this.models,
- required this.selectedModel,
- required this.onSelectModel,
- });
-
- final List models;
- final AIModelPB? selectedModel;
- final void Function(AIModelPB) onSelectModel;
-
- @override
- Widget build(BuildContext context) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: models
- .mapIndexed(
- (index, model) => _buildModelButton(model, index == 0),
- )
- .toList(),
- );
- }
-
- Widget _buildModelButton(
- AIModelPB model, [
- bool isFirst = false,
- ]) {
- return FlowyOptionTile.checkbox(
- text: model.name,
- isSelected: model == selectedModel,
- showTopBorder: isFirst,
- onTap: () {
- onSelectModel(model);
- },
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart
index 1e7d428263..e14275ca30 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart
@@ -64,12 +64,8 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> {
super.didUpdateWidget(oldWidget);
if (oldWidget.markdown != widget.markdown) {
- final editorState = _parseMarkdown(
- widget.markdown.trim(),
- previousDocument: this.editorState.document,
- );
- this.editorState.dispose();
- this.editorState = editorState;
+ editorState.dispose();
+ editorState = _parseMarkdown(widget.markdown.trim());
scrollController.dispose();
scrollController = EditorScrollController(
editorState: editorState,
@@ -133,30 +129,8 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> {
);
}
- EditorState _parseMarkdown(
- String markdown, {
- Document? previousDocument,
- }) {
- // merge the nodes from the previous document with the new document to keep the same node ids
+ EditorState _parseMarkdown(String markdown) {
final document = customMarkdownToDocument(markdown);
- final documentIterator = NodeIterator(
- document: document,
- startNode: document.root,
- );
- if (previousDocument != null) {
- final previousDocumentIterator = NodeIterator(
- document: previousDocument,
- startNode: previousDocument.root,
- );
- while (
- documentIterator.moveNext() && previousDocumentIterator.moveNext()) {
- final currentNode = documentIterator.current;
- final previousNode = previousDocumentIterator.current;
- if (currentNode.path.equals(previousNode.path)) {
- currentNode.id = previousNode.id;
- }
- }
- }
final editorState = EditorState(document: document);
return editorState;
}
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart
index 08fd82188d..48a8459598 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart
@@ -21,7 +21,6 @@ import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_result/appflowy_result.dart';
@@ -42,7 +41,6 @@ class AIMessageActionBar extends StatefulWidget {
required this.showDecoration,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
this.onOverrideVisibility,
});
@@ -50,7 +48,6 @@ class AIMessageActionBar extends StatefulWidget {
final bool showDecoration;
final void Function()? onRegenerate;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
final void Function(bool)? onOverrideVisibility;
@override
@@ -129,12 +126,6 @@ class _AIMessageActionBarState extends State {
popoverMutex: popoverMutex,
onOverrideVisibility: widget.onOverrideVisibility,
),
- ChangeModelButton(
- isInHoverBar: widget.showDecoration,
- onRegenerate: widget.onChangeModel,
- popoverMutex: popoverMutex,
- onOverrideVisibility: widget.onOverrideVisibility,
- ),
SaveToPageButton(
textMessage: widget.message as TextMessage,
isInHoverBar: widget.showDecoration,
@@ -184,7 +175,8 @@ class CopyButton extends StatelessWidget {
);
if (context.mounted) {
showToastNotification(
- message: LocaleKeys.message_copy_success.tr(),
+ context,
+ message: LocaleKeys.grid_url_copiedNotification.tr(),
);
}
},
@@ -268,11 +260,8 @@ class _ChangeFormatButtonState extends State {
constraints: const BoxConstraints(),
onClose: () => widget.onOverrideVisibility?.call(false),
child: buildButton(context),
- popupBuilder: (_) => BlocProvider.value(
- value: context.read(),
- child: _ChangeFormatPopoverContent(
- onRegenerate: widget.onRegenerate,
- ),
+ popupBuilder: (_) => _ChangeFormatPopoverContent(
+ onRegenerate: widget.onRegenerate,
),
);
}
@@ -370,16 +359,11 @@ class _ChangeFormatPopoverContentState
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
- BlocBuilder(
- builder: (context, state) {
- return ChangeFormatBar(
- spacing: 2.0,
- showImageFormats: state.aiType.isCloud,
- predefinedFormat: predefinedFormat,
- onSelectPredefinedFormat: (format) {
- setState(() => predefinedFormat = format);
- },
- );
+ ChangeFormatBar(
+ spacing: 2.0,
+ predefinedFormat: predefinedFormat,
+ onSelectPredefinedFormat: (format) {
+ setState(() => predefinedFormat = format);
},
),
const HSpace(4.0),
@@ -413,85 +397,6 @@ class _ChangeFormatPopoverContentState
}
}
-class ChangeModelButton extends StatefulWidget {
- const ChangeModelButton({
- super.key,
- required this.isInHoverBar,
- this.popoverMutex,
- this.onRegenerate,
- this.onOverrideVisibility,
- });
-
- final bool isInHoverBar;
- final PopoverMutex? popoverMutex;
- final void Function(AIModelPB)? onRegenerate;
- final void Function(bool)? onOverrideVisibility;
-
- @override
- State createState() => _ChangeModelButtonState();
-}
-
-class _ChangeModelButtonState extends State {
- final popoverController = PopoverController();
-
- @override
- Widget build(BuildContext context) {
- return AppFlowyPopover(
- controller: popoverController,
- mutex: widget.popoverMutex,
- triggerActions: PopoverTriggerFlags.none,
- margin: EdgeInsets.zero,
- offset: Offset(8, 0),
- direction: PopoverDirection.rightWithBottomAligned,
- constraints: BoxConstraints(maxWidth: 250, maxHeight: 600),
- onClose: () => widget.onOverrideVisibility?.call(false),
- child: buildButton(context),
- popupBuilder: (_) {
- final bloc = context.read();
- final (models, _) = bloc.aiModelStateNotifier.getAvailableModels();
- return SelectModelPopoverContent(
- models: models,
- selectedModel: null,
- onSelectModel: widget.onRegenerate,
- );
- },
- );
- }
-
- Widget buildButton(BuildContext context) {
- return FlowyTooltip(
- message: LocaleKeys.chat_switchModel_label.tr(),
- child: FlowyIconButton(
- width: 32.0,
- height: DesktopAIChatSizes.messageActionBarIconSize,
- hoverColor: AFThemeExtension.of(context).lightGreyHover,
- radius: widget.isInHoverBar
- ? DesktopAIChatSizes.messageHoverActionBarIconRadius
- : DesktopAIChatSizes.messageActionBarIconRadius,
- icon: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- FlowySvg(
- FlowySvgs.ai_sparks_s,
- color: Theme.of(context).hintColor,
- size: const Size.square(16),
- ),
- FlowySvg(
- FlowySvgs.ai_source_drop_down_s,
- color: Theme.of(context).hintColor,
- size: const Size.square(8),
- ),
- ],
- ),
- onPressed: () {
- widget.onOverrideVisibility?.call(true);
- popoverController.show();
- },
- ),
- );
- }
-}
-
class SaveToPageButton extends StatefulWidget {
const SaveToPageButton({
super.key,
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart
index 2786799520..a938c9094f 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart
@@ -12,7 +12,6 @@ import 'package:appflowy/shared/markdown_to_document.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -24,7 +23,6 @@ import 'package:universal_platform/universal_platform.dart';
import '../chat_avatar.dart';
import '../layout_define.dart';
-import 'ai_change_model_bottom_sheet.dart';
import 'ai_message_action_bar.dart';
import 'ai_change_format_bottom_sheet.dart';
import 'message_util.dart';
@@ -43,7 +41,6 @@ class ChatAIMessageBubble extends StatelessWidget {
this.isSelectingMessages = false,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
});
final Message message;
@@ -53,7 +50,6 @@ class ChatAIMessageBubble extends StatelessWidget {
final bool isSelectingMessages;
final void Function()? onRegenerate;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
@override
Widget build(BuildContext context) {
@@ -77,7 +73,6 @@ class ChatAIMessageBubble extends StatelessWidget {
message: message,
onRegenerate: onRegenerate,
onChangeFormat: onChangeFormat,
- onChangeModel: onChangeModel,
child: child,
);
}
@@ -87,7 +82,6 @@ class ChatAIMessageBubble extends StatelessWidget {
message: message,
onRegenerate: onRegenerate,
onChangeFormat: onChangeFormat,
- onChangeModel: onChangeModel,
child: child,
);
}
@@ -97,7 +91,6 @@ class ChatAIMessageBubble extends StatelessWidget {
message: message,
onRegenerate: onRegenerate,
onChangeFormat: onChangeFormat,
- onChangeModel: onChangeModel,
child: child,
);
}
@@ -110,14 +103,12 @@ class ChatAIBottomInlineActions extends StatelessWidget {
required this.message,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
});
final Widget child;
final Message message;
final void Function()? onRegenerate;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
@override
Widget build(BuildContext context) {
@@ -136,7 +127,6 @@ class ChatAIBottomInlineActions extends StatelessWidget {
showDecoration: false,
onRegenerate: onRegenerate,
onChangeFormat: onChangeFormat,
- onChangeModel: onChangeModel,
),
),
const VSpace(32.0),
@@ -152,14 +142,12 @@ class ChatAIMessageHover extends StatefulWidget {
required this.message,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
});
final Widget child;
final Message message;
final void Function()? onRegenerate;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
@override
State createState() => _ChatAIMessageHoverState();
@@ -229,23 +217,22 @@ class _ChatAIMessageHoverState extends State {
setState(() => hoverActionBar = false);
}
},
- child: SizedBox(
- width: 784,
- height: DesktopAIChatSizes.messageActionBarIconSize +
- DesktopAIChatSizes.messageHoverActionBarPadding.vertical,
+ child: Container(
+ constraints: BoxConstraints(
+ maxWidth: 784,
+ maxHeight: DesktopAIChatSizes.messageActionBarIconSize +
+ DesktopAIChatSizes
+ .messageHoverActionBarPadding.vertical,
+ ),
child: hoverBubble || hoverActionBar || overrideVisibility
- ? Align(
- alignment: AlignmentDirectional.centerStart,
- child: AIMessageActionBar(
- message: widget.message,
- showDecoration: true,
- onRegenerate: widget.onRegenerate,
- onChangeFormat: widget.onChangeFormat,
- onChangeModel: widget.onChangeModel,
- onOverrideVisibility: (visibility) {
- overrideVisibility = visibility;
- },
- ),
+ ? AIMessageActionBar(
+ message: widget.message,
+ showDecoration: true,
+ onRegenerate: widget.onRegenerate,
+ onChangeFormat: widget.onChangeFormat,
+ onOverrideVisibility: (visibility) {
+ overrideVisibility = visibility;
+ },
)
: null,
),
@@ -315,14 +302,12 @@ class ChatAIMessagePopup extends StatelessWidget {
required this.message,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
});
final Widget child;
final Message message;
final void Function()? onRegenerate;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
@override
Widget build(BuildContext context) {
@@ -343,8 +328,6 @@ class ChatAIMessagePopup extends StatelessWidget {
_divider(),
_changeFormatButton(context),
_divider(),
- _changeModelButton(context),
- _divider(),
_saveToPageButton(context),
],
);
@@ -376,7 +359,8 @@ class ChatAIMessagePopup extends StatelessWidget {
}
if (context.mounted) {
showToastNotification(
- message: LocaleKeys.message_copy_success.tr(),
+ context,
+ message: LocaleKeys.grid_url_copiedNotification.tr(),
);
}
},
@@ -415,25 +399,6 @@ class ChatAIMessagePopup extends StatelessWidget {
);
}
- Widget _changeModelButton(BuildContext context) {
- return MobileQuickActionButton(
- onTap: () async {
- final bloc = context.read();
- final (models, _) = bloc.aiModelStateNotifier.getAvailableModels();
- final result = await showChangeModelBottomSheet(context, models);
- if (result != null) {
- onChangeModel?.call(result);
- if (context.mounted) {
- Navigator.of(context).pop();
- }
- }
- },
- icon: FlowySvgs.ai_sparks_s,
- iconSize: const Size.square(20),
- text: LocaleKeys.chat_switchModel_label.tr(),
- );
- }
-
Widget _saveToPageButton(BuildContext context) {
return MobileQuickActionButton(
onTap: () async {
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart
index 380767105f..d6b3c87903 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart
@@ -4,7 +4,6 @@ import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_message_stream.dart';
-import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -33,11 +32,9 @@ class ChatAIMessageWidget extends StatelessWidget {
required this.questionId,
required this.chatId,
required this.refSourceJsonString,
- required this.onStopStream,
this.onSelectedMetadata,
this.onRegenerate,
this.onChangeFormat,
- this.onChangeModel,
this.isLastMessage = false,
this.isStreaming = false,
this.isSelectingMessages = false,
@@ -53,9 +50,7 @@ class ChatAIMessageWidget extends StatelessWidget {
final String? refSourceJsonString;
final void Function(ChatMessageRefSource metadata)? onSelectedMetadata;
final void Function()? onRegenerate;
- final void Function() onStopStream;
final void Function(PredefinedFormat)? onChangeFormat;
- final void Function(AIModelPB)? onChangeModel;
final bool isStreaming;
final bool isLastMessage;
final bool isSelectingMessages;
@@ -113,13 +108,10 @@ class ChatAIMessageWidget extends StatelessWidget {
isSelectingMessages: isSelectingMessages,
onRegenerate: onRegenerate,
onChangeFormat: onChangeFormat,
- onChangeModel: onChangeModel,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- AIMarkdownText(
- markdown: state.text,
- ),
+ AIMarkdownText(markdown: state.text),
if (state.sources.isNotEmpty)
SelectionContainer.disabled(
child: AIMessageMetadata(
@@ -154,15 +146,6 @@ class ChatAIMessageWidget extends StatelessWidget {
errorMessage: message,
);
},
- onInitializingLocalAI: () {
- onStopStream();
-
- return ChatErrorMessageWidget(
- errorMessage: LocaleKeys
- .settings_aiPage_keys_localAIInitializing
- .tr(),
- );
- },
),
),
);
diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart
index 652fe3791b..1b0084c77c 100644
--- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart
+++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart
@@ -14,6 +14,7 @@ import 'package:universal_platform/universal_platform.dart';
void openPageFromMessage(BuildContext context, ViewPB? view) {
if (view == null) {
showToastNotification(
+ context,
message: LocaleKeys.chat_openPagePreviewFailedToast.tr(),
type: ToastificationType.error,
);
@@ -35,6 +36,7 @@ void showSaveMessageSuccessToast(BuildContext context, ViewPB? view) {
return;
}
showToastNotification(
+ context,
richMessage: TextSpan(
children: [
TextSpan(
diff --git a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart
index ebda487515..b25bb5af06 100644
--- a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart
+++ b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart
@@ -36,7 +36,7 @@ class BlankPagePlugin extends Plugin {
PluginWidgetBuilder get widgetBuilder => BlankPagePluginWidgetBuilder();
@override
- PluginId get id => "";
+ PluginId get id => "BlankStack";
@override
PluginType get pluginType => PluginType.blank;
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart
index 73b2d2977b..df159b817b 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart
@@ -47,6 +47,15 @@ class NumberCellBloc extends Bloc {
if (state.content != text) {
emit(state.copyWith(content: text));
await cellController.saveCellData(text);
+
+ // If the input content is "abc" that can't parsered as number then the data stored in the backend will be an empty string.
+ // So for every cell data that will be formatted in the backend.
+ // It needs to get the formatted data after saving.
+ add(
+ NumberCellEvent.didReceiveCellUpdate(
+ cellController.getCellData(),
+ ),
+ );
}
},
);
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart
index ec789b03a0..70c5e074ab 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart
@@ -143,11 +143,12 @@ class RelationCellBloc extends Bloc {
(f) => null,
);
if (databaseMeta != null) {
- final result = await ViewBackendService.getView(databaseMeta.viewId);
+ final result =
+ await ViewBackendService.getView(databaseMeta.inlineViewId);
return result.fold(
(s) => DatabaseMeta(
databaseId: databaseId,
- viewId: databaseMeta.viewId,
+ inlineViewId: databaseMeta.inlineViewId,
databaseName: s.name,
),
(f) => null,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart
index c6e4e6484b..f8ed915b62 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart
@@ -241,11 +241,6 @@ class SelectOptionCellEditorBloc
} else if (!state.selectedOptions
.any((option) => option.id == focusedOptionId)) {
_selectOptionService.select(optionIds: [focusedOptionId]);
- emit(
- state.copyWith(
- clearFilter: true,
- ),
- );
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart
index 93fd69bcfc..8370bd9bff 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart
@@ -411,28 +411,23 @@ class FieldController {
/// Listen for field setting changes in the backend.
void _listenOnFieldSettingsChanged() {
FieldInfo? updateFieldSettings(FieldSettingsPB updatedFieldSettings) {
- final newFields = [...fieldInfos];
+ final List newFields = fieldInfos;
+ var updatedField = newFields.firstOrNull;
- if (newFields.isEmpty) {
+ if (updatedField == null) {
return null;
}
final index = newFields
.indexWhere((field) => field.id == updatedFieldSettings.fieldId);
-
if (index != -1) {
newFields[index] =
newFields[index].copyWith(fieldSettings: updatedFieldSettings);
- _fieldNotifier.fieldInfos = newFields;
- _fieldSettings
- ..removeWhere(
- (field) => field.fieldId == updatedFieldSettings.fieldId,
- )
- ..add(updatedFieldSettings);
- return newFields[index];
+ updatedField = newFields[index];
}
- return null;
+ _fieldNotifier.fieldInfos = newFields;
+ return updatedField;
}
_fieldSettingsListener.start(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart
index 4ddde80b79..691b6b7227 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart
@@ -17,11 +17,11 @@ class RelationDatabaseListCubit extends Cubit {
.send()
.fold>((s) => s.items, (f) => []);
final futures = metaPBs.map((meta) {
- return ViewBackendService.getView(meta.viewId).then(
+ return ViewBackendService.getView(meta.inlineViewId).then(
(result) => result.fold(
(s) => DatabaseMeta(
databaseId: meta.databaseId,
- viewId: meta.viewId,
+ inlineViewId: meta.inlineViewId,
databaseName: s.name,
),
(f) => null,
@@ -43,10 +43,10 @@ class DatabaseMeta with _$DatabaseMeta {
/// id of the database
required String databaseId,
- /// id of the view
- required String viewId,
+ /// id of the inline view
+ required String inlineViewId,
- /// name of the database
+ /// name of the database, currently identical to the name of the inline view
required String databaseName,
}) = _DatabaseMeta;
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
index f735618dd8..4f975cd1a6 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart
@@ -73,24 +73,27 @@ class RelatedRowDetailPageBloc
});
}
+ /// initialize bloc through the `database_id` and `row_id`. The process is as
+ /// follows:
+ /// 1. use the `database_id` to get the database meta, which contains the
+ /// `inline_view_id`
+ /// 2. use the `inline_view_id` to instantiate a `DatabaseController`.
+ /// 3. use the `row_id` with the DatabaseController` to create `RowController`
void _init(String databaseId, String initialRowId) async {
- final viewId = await DatabaseEventGetDefaultDatabaseViewId(
- DatabaseIdPB(value: databaseId),
- ).send().fold(
- (pb) => pb.value,
- (error) => null,
- );
-
- if (viewId == null) {
+ final databaseMeta =
+ await DatabaseEventGetDatabaseMeta(DatabaseIdPB(value: databaseId))
+ .send()
+ .fold((s) => s, (f) => null);
+ if (databaseMeta == null) {
return;
}
-
- final databaseView = await ViewBackendService.getView(viewId)
- .fold((viewPB) => viewPB, (f) => null);
- if (databaseView == null) {
+ final inlineView =
+ await ViewBackendService.getView(databaseMeta.inlineViewId)
+ .fold((viewPB) => viewPB, (f) => null);
+ if (inlineView == null) {
return;
}
- final databaseController = DatabaseController(view: databaseView);
+ final databaseController = DatabaseController(view: inlineView);
await databaseController.open().fold(
(s) => databaseController.setIsLoading(false),
(f) => null,
@@ -101,7 +104,7 @@ class RelatedRowDetailPageBloc
}
final rowController = RowController(
rowMeta: rowInfo.rowMeta,
- viewId: databaseView.id,
+ viewId: inlineView.id,
rowCache: databaseController.rowCache,
);
diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart
index 351dea2cd8..ae0b9173c7 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart
@@ -30,9 +30,9 @@ class DatabaseSyncBloc extends Bloc {
.then((value) => value.fold((s) => s, (f) => null));
emit(
state.copyWith(
- shouldShowIndicator:
- userProfile?.workspaceAuthType == AuthTypePB.Server &&
- databaseId != null,
+ shouldShowIndicator: userProfile?.authenticator ==
+ AuthenticatorPB.AppFlowyCloud &&
+ databaseId != null,
),
);
if (databaseId != null) {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
index 70d00bcd25..77a26a9c58 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart
@@ -386,15 +386,15 @@ class _BoardContentState extends State<_BoardContent> {
scrollManager: scrollManager,
),
),
- cardBuilder: (cardContext, column, columnItem) =>
+ cardBuilder: (context, column, columnItem) =>
MultiBlocProvider(
key: ValueKey("board_card_${column.id}_${columnItem.id}"),
providers: [
BlocProvider.value(
- value: cardContext.read(),
+ value: context.read(),
),
BlocProvider.value(
- value: cardContext.read(),
+ value: context.read(),
),
BlocProvider(
create: (_) => ViewLockStatusBloc(view: widget.view)
@@ -402,7 +402,7 @@ class _BoardContentState extends State<_BoardContent> {
),
],
child: BlocBuilder(
- builder: (lockStatusContext, state) {
+ builder: (context, state) {
return IgnorePointer(
ignoring: state.isLocked,
child: _BoardCard(
@@ -412,13 +412,6 @@ class _BoardContentState extends State<_BoardContent> {
notifier: widget.focusScope,
cellBuilder: cellBuilder,
compactMode: compactMode,
- onOpenCard: (rowMeta) => _openCard(
- context: context,
- databaseController: lockStatusContext
- .read()
- .databaseController,
- rowMeta: rowMeta,
- ),
),
);
},
@@ -588,7 +581,6 @@ class _BoardCard extends StatefulWidget {
required this.cellBuilder,
required this.notifier,
required this.compactMode,
- required this.onOpenCard,
});
final AppFlowyGroupData afGroupData;
@@ -597,7 +589,6 @@ class _BoardCard extends StatefulWidget {
final CardCellBuilder cellBuilder;
final BoardFocusScope notifier;
final bool compactMode;
- final void Function(RowMetaPB) onOpenCard;
@override
State<_BoardCard> createState() => _BoardCardState();
@@ -707,8 +698,10 @@ class _BoardCardState extends State<_BoardCard> {
groupingFieldId: widget.groupItem.fieldInfo.id,
isEditing: _isEditing,
cellBuilder: widget.cellBuilder,
- onTap: (context) => widget.onOpenCard(
- context.read().rowController.rowMeta,
+ onTap: (context) => _openCard(
+ context: context,
+ databaseController: databaseController,
+ rowMeta: context.read().rowController.rowMeta,
),
onShiftTap: (_) {
Focus.of(context).requestFocus();
@@ -854,7 +847,7 @@ class _BoardTrailingState extends State {
suffixIcon: Padding(
padding: const EdgeInsets.only(left: 4, bottom: 8.0),
child: FlowyIconButton(
- icon: const FlowySvg(FlowySvgs.close_filled_s),
+ icon: const FlowySvg(FlowySvgs.close_filled_m),
hoverColor: Colors.transparent,
onPressed: () => _textController.clear(),
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart
index 915bf70a61..23c2fe1f91 100755
--- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart
@@ -108,7 +108,7 @@ class _GridFieldCellState extends State {
top: 0,
bottom: 0,
right: 0,
- child: DragToExpandLine(),
+ child: _DragToExpandLine(),
);
return _GridHeaderCellContainer(
@@ -158,11 +158,8 @@ class _GridHeaderCellContainer extends StatelessWidget {
}
}
-@visibleForTesting
-class DragToExpandLine extends StatelessWidget {
- const DragToExpandLine({
- super.key,
- });
+class _DragToExpandLine extends StatelessWidget {
+ const _DragToExpandLine();
@override
Widget build(BuildContext context) {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
index 7c2dc40869..3554f9112e 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart
@@ -362,13 +362,9 @@ const kDatabasePluginWidgetBuilderActionBuilder = 'action_builder';
const kDatabasePluginWidgetBuilderNode = 'node';
class DatabasePluginWidgetBuilderSize {
- const DatabasePluginWidgetBuilderSize({
- required this.horizontalPadding,
- this.verticalPadding = 16.0,
- });
+ const DatabasePluginWidgetBuilderSize({required this.horizontalPadding});
final double horizontalPadding;
- final double verticalPadding;
}
class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart
index ab0533819a..b10d63d2d4 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
@@ -14,7 +16,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
-import 'package:universal_platform/universal_platform.dart';
import '../editable_cell_skeleton/checklist.dart';
@@ -200,16 +201,19 @@ class _ChecklistItems extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(),
proxyDecorator: (child, index, _) => Material(
color: Colors.transparent,
- child: MouseRegion(
- cursor: UniversalPlatform.isWindows
- ? SystemMouseCursors.click
- : SystemMouseCursors.grabbing,
- child: IgnorePointer(
- child: BlocProvider.value(
+ child: Stack(
+ children: [
+ BlocProvider.value(
value: context.read(),
child: child,
),
- ),
+ MouseRegion(
+ cursor: Platform.isWindows
+ ? SystemMouseCursors.click
+ : SystemMouseCursors.grabbing,
+ child: const SizedBox.expand(),
+ ),
+ ],
),
),
buildDefaultDragHandles: false,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart
index 39616dbcf8..0dc7779e55 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart
@@ -203,7 +203,7 @@ class MobileURLEditor extends StatelessWidget {
ClipboardData(text: textEditingController.text),
);
Fluttertoast.showToast(
- msg: LocaleKeys.message_copy_success.tr(),
+ msg: LocaleKeys.grid_url_copiedNotification.tr(),
gravity: ToastGravity.BOTTOM,
);
context.pop();
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
index 9853f9c1bd..b788d6bd38 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart
@@ -14,7 +14,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:universal_platform/universal_platform.dart';
import '../../application/cell/bloc/checklist_cell_bloc.dart';
import 'checklist_cell_textfield.dart';
@@ -126,16 +125,19 @@ class ChecklistItemList extends StatelessWidget {
shrinkWrap: true,
proxyDecorator: (child, index, _) => Material(
color: Colors.transparent,
- child: MouseRegion(
- cursor: UniversalPlatform.isWindows
- ? SystemMouseCursors.click
- : SystemMouseCursors.grabbing,
- child: IgnorePointer(
- child: BlocProvider.value(
+ child: Stack(
+ children: [
+ BlocProvider.value(
value: context.read(),
child: child,
),
- ),
+ MouseRegion(
+ cursor: Platform.isWindows
+ ? SystemMouseCursors.click
+ : SystemMouseCursors.grabbing,
+ child: const SizedBox.expand(),
+ ),
+ ],
),
),
buildDefaultDragHandles: false,
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
index 7f6960de9d..e68e77cd97 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart
@@ -256,7 +256,7 @@ class _CellEditorTitle extends StatelessWidget {
}
void _openRelatedDatbase(BuildContext context) {
- FolderEventGetView(ViewIdPB(value: databaseMeta.viewId))
+ FolderEventGetView(ViewIdPB(value: databaseMeta.inlineViewId))
.send()
.then((result) {
result.fold(
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart
index debbb467e7..8d64c537c3 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart
@@ -21,8 +21,8 @@ import 'package:appflowy/shared/af_image.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
@@ -69,8 +69,8 @@ class RowBanner extends StatefulWidget {
class _RowBannerState extends State {
final _isHovering = ValueNotifier(false);
late final isLocalMode =
- (widget.userProfile?.workspaceAuthType ?? AuthTypePB.Local) ==
- AuthTypePB.Local;
+ (widget.userProfile?.authenticator ?? AuthenticatorPB.Local) ==
+ AuthenticatorPB.Local;
@override
void dispose() {
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart
index 436dbd085d..0489db8907 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart
@@ -5,12 +5,10 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
-import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
@@ -73,16 +71,9 @@ class _RowEditor extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return MultiBlocProvider(
- providers: [
- BlocProvider(
- create: (_) => DocumentBloc(documentId: view.id)
- ..add(const DocumentEvent.initial()),
- ),
- BlocProvider(
- create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()),
- ),
- ],
+ return BlocProvider(
+ create: (_) =>
+ DocumentBloc(documentId: view.id)..add(const DocumentEvent.initial()),
child: BlocConsumer(
listenWhen: (previous, current) =>
previous.isDocumentEmpty != current.isDocumentEmpty,
@@ -121,34 +112,29 @@ class _RowEditor extends StatelessWidget {
return context;
},
dispose: (_, editorContext) => editorContext.dispose(),
- child: AiWriterScrollWrapper(
+ child: EditorDropHandler(
viewId: view.id,
editorState: editorState,
- child: EditorDropHandler(
+ isLocalMode: context.read().isLocalMode,
+ dropManagerState: context.read(),
+ child: EditorTransactionService(
viewId: view.id,
editorState: editorState,
- isLocalMode: context.read().isLocalMode,
- dropManagerState: context.read(),
- child: EditorTransactionService(
- viewId: view.id,
- editorState: editorState,
- child: Provider(
- create: (context) => DatabasePluginWidgetBuilderSize(
- horizontalPadding: 0,
- ),
- child: AppFlowyEditorPage(
- shrinkWrap: true,
- autoFocus: false,
- editorState: editorState,
- styleCustomizer: EditorStyleCustomizer(
- context: context,
- padding: const EdgeInsets.only(left: 16, right: 54),
- ),
- showParagraphPlaceholder: (editorState, _) =>
- editorState.document.isEmpty,
- placeholderText: (_) =>
- LocaleKeys.cardDetails_notesPlaceholder.tr(),
+ child: Provider(
+ create: (context) =>
+ DatabasePluginWidgetBuilderSize(horizontalPadding: 0),
+ child: AppFlowyEditorPage(
+ shrinkWrap: true,
+ autoFocus: false,
+ editorState: editorState,
+ styleCustomizer: EditorStyleCustomizer(
+ context: context,
+ padding: const EdgeInsets.only(left: 16, right: 54),
),
+ showParagraphPlaceholder: (editorState, _) =>
+ editorState.document.isEmpty,
+ placeholderText: (_) =>
+ LocaleKeys.cardDetails_notesPlaceholder.tr(),
),
),
),
diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart
index c7bc286371..5f40959c02 100644
--- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart
@@ -24,11 +24,11 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction {
case DatabaseSettingAction.showProperties:
return FlowySvgs.multiselect_s;
case DatabaseSettingAction.showLayout:
- return FlowySvgs.database_layout_s;
+ return FlowySvgs.database_layout_m;
case DatabaseSettingAction.showGroup:
return FlowySvgs.group_s;
case DatabaseSettingAction.showCalendarLayout:
- return FlowySvgs.calendar_layout_s;
+ return FlowySvgs.calendar_layout_m;
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart
index ee52be8c26..eaa82b22e9 100644
--- a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart
@@ -1,4 +1,3 @@
-import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/row/row_detail_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
@@ -8,10 +7,8 @@ import 'package:appflowy/plugins/database/widgets/row/row_property.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
+import 'package:appflowy/plugins/document/presentation/editor_notification.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/shared/flowy_error_page.dart';
@@ -21,12 +18,8 @@ import 'package:appflowy/workspace/application/action_navigation/navigation_acti
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
-import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
-import 'package:provider/provider.dart';
-
-import '../../workspace/application/view/view_bloc.dart';
// This widget is largely copied from `plugins/document/document_page.dart` intentionally instead of opting for an abstraction. We can make an abstraction after the view refactor is done and there's more clarity in that department.
@@ -53,6 +46,18 @@ class DatabaseDocumentPage extends StatefulWidget {
class _DatabaseDocumentPageState extends State {
EditorState? editorState;
+ @override
+ void initState() {
+ super.initState();
+ EditorNotification.addListener(_onEditorNotification);
+ }
+
+ @override
+ void dispose() {
+ EditorNotification.removeListener(_onEditorNotification);
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
@@ -67,10 +72,6 @@ class _DatabaseDocumentPageState extends State {
documentId: widget.documentId,
)..add(const DocumentEvent.initial()),
),
- BlocProvider(
- create: (_) =>
- ViewBloc(view: widget.view)..add(const ViewEvent.initial()),
- ),
],
child: BlocBuilder(
builder: (context, state) {
@@ -97,11 +98,7 @@ class _DatabaseDocumentPageState extends State {
return BlocListener(
listener: _onNotificationAction,
listenWhen: (_, curr) => curr.action != null,
- child: AiWriterScrollWrapper(
- viewId: widget.view.id,
- editorState: editorState,
- child: _buildEditorPage(context, state),
- ),
+ child: _buildEditorPage(context, state),
);
},
),
@@ -118,34 +115,21 @@ class _DatabaseDocumentPageState extends State {
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: EditorStyleCustomizer.documentPadding,
- editorState: state.editorState!,
),
header: _buildDatabaseDataContent(context, state.editorState!),
initialSelection: widget.initialSelection,
useViewInfoBloc: false,
- placeholderText: (node) =>
- node.type == ParagraphBlockKeys.type && !node.isInTable
- ? LocaleKeys.editor_slashPlaceHolder.tr()
- : '',
),
);
- return Provider(
- create: (_) {
- final context = SharedEditorContext();
- context.isInDatabaseRowPage = true;
- return context;
- },
- dispose: (_, editorContext) => editorContext.dispose(),
- child: EditorTransactionService(
- viewId: widget.view.id,
- editorState: state.editorState!,
- child: Column(
- children: [
- if (state.isDeleted) _buildBanner(context),
- Expanded(child: appflowyEditorPage),
- ],
- ),
+ return EditorTransactionService(
+ viewId: widget.view.id,
+ editorState: state.editorState!,
+ child: Column(
+ children: [
+ if (state.isDeleted) _buildBanner(context),
+ Expanded(child: appflowyEditorPage),
+ ],
),
);
}
@@ -218,6 +202,20 @@ class _DatabaseDocumentPageState extends State {
);
}
+ void _onEditorNotification(EditorNotificationType type) {
+ final editorState = this.editorState;
+ if (editorState == null) {
+ return;
+ }
+ if (type == EditorNotificationType.undo) {
+ undoCommand.execute(editorState);
+ } else if (type == EditorNotificationType.redo) {
+ redoCommand.execute(editorState);
+ } else if (type == EditorNotificationType.exitEditing) {
+ editorState.selection = null;
+ }
+ }
+
void _onNotificationAction(
BuildContext context,
ActionNavigationState state,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart
index 264ec4bb11..268863664b 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart
@@ -101,8 +101,8 @@ class DocumentBloc extends Bloc {
bool get isLocalMode {
final userProfilePB = state.userProfilePB;
- final type = userProfilePB?.workspaceAuthType ?? AuthTypePB.Local;
- return type == AuthTypePB.Local;
+ final type = userProfilePB?.authenticator ?? AuthenticatorPB.Local;
+ return type == AuthenticatorPB.Local;
}
@override
@@ -272,14 +272,12 @@ class DocumentBloc extends Bloc {
}
if (options.inMemoryUpdate) {
- if (enableDocumentInternalLog) {
- Log.trace('skip transaction for in-memory update');
- }
+ Log.info('skip transaction for in-memory update');
return;
}
if (enableDocumentInternalLog) {
- Log.trace(
+ Log.debug(
'[TransactionAdapter] 1. transaction before apply: ${transaction.hashCode}',
);
}
@@ -291,7 +289,7 @@ class DocumentBloc extends Bloc {
await _documentRules.applyRules(value: value);
if (enableDocumentInternalLog) {
- Log.trace(
+ Log.debug(
'[TransactionAdapter] 4. transaction after apply: ${transaction.hashCode}',
);
}
@@ -442,6 +440,7 @@ class DocumentBloc extends Bloc {
final context = AppGlobals.rootNavKey.currentContext;
if (context != null && context.mounted) {
showToastNotification(
+ context,
message: 'document integrity check failed',
type: ToastificationType.error,
);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
index a0678372cf..74a6199b89 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart
@@ -32,7 +32,7 @@ class DocumentCollaboratorsBloc
emit(
state.copyWith(
shouldShowIndicator:
- userProfile?.workspaceAuthType == AuthTypePB.Server,
+ userProfile?.authenticator == AuthenticatorPB.AppFlowyCloud,
),
);
final deviceId = ApplicationInfo.deviceId;
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart
index 38bf2bcd14..574ae34af8 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart
@@ -13,6 +13,7 @@ import 'package:appflowy_editor/appflowy_editor.dart'
NodeIterator,
NodeExternalValues,
HeadingBlockKeys,
+ QuoteBlockKeys,
NumberedListBlockKeys,
BulletedListBlockKeys,
blockComponentDelta;
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart
index f530b1ef8d..230a5b8fa7 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart
@@ -104,28 +104,6 @@ class DocumentRules {
} else {
// otherwise, delete the column
deleteColumnsTransaction.deleteNode(column);
-
- final deletedColumnRatio =
- column.attributes[SimpleColumnBlockKeys.ratio];
- if (deletedColumnRatio != null) {
- // update the ratio of the columns
- final columnsNode = column.columnsParent;
- if (columnsNode != null) {
- final length = columnsNode.children.length;
- for (final columnNode in columnsNode.children) {
- final ratio =
- columnNode.attributes[SimpleColumnBlockKeys.ratio] ??
- 1.0 / length;
- if (ratio != null) {
- deleteColumnsTransaction.updateNode(columnNode, {
- ...columnNode.attributes,
- SimpleColumnBlockKeys.ratio:
- ratio + deletedColumnRatio / (length - 1),
- });
- }
- }
- }
- }
}
}
}
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart
index 7254539809..0fae90920d 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart
@@ -31,7 +31,7 @@ class DocumentSyncBloc extends Bloc {
emit(
state.copyWith(
shouldShowIndicator:
- userProfile?.workspaceAuthType == AuthTypePB.Server,
+ userProfile?.authenticator == AuthenticatorPB.AppFlowyCloud,
),
);
_syncStateListener.start(
diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
index 8716bb7ae2..5c695fa508 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart
@@ -5,7 +5,6 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/banner.dart';
import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
-import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
@@ -89,12 +88,9 @@ class _DocumentPageState extends State
BlocProvider.value(value: documentBloc),
BlocProvider.value(
value: ViewLockStatusBloc(view: widget.view)
- ..add(ViewLockStatusEvent.initial()),
- ),
- BlocProvider(
- create: (context) =>
- ViewBloc(view: widget.view)..add(const ViewEvent.initial()),
- lazy: false,
+ ..add(
+ ViewLockStatusEvent.initial(),
+ ),
),
],
child: BlocConsumer(
@@ -128,20 +124,14 @@ class _DocumentPageState extends State
return const SizedBox.shrink();
}
- return MultiBlocListener(
- listeners: [
- BlocListener