From 0dae8cf2f972b2199968e1e157f61d8e104d41eb Mon Sep 17 00:00:00 2001 From: "Kilu.He" <108015703+qinluhe@users.noreply.github.com> Date: Wed, 12 Jul 2023 11:54:50 +0800 Subject: [PATCH] feat: support i18n in typescript (#2948) --- frontend/appflowy_tauri/.gitignore | 3 +- frontend/appflowy_tauri/package.json | 15 +- frontend/appflowy_tauri/pnpm-lock.yaml | 197 ++++++++++-- .../appflowy_tauri/scripts/i18n/index.cjs | 53 ++++ .../src/appflowy_app/@types/i18next.d.ts | 8 + .../src/appflowy_app/@types/resources.ts | 7 + .../appflowy_tauri/src/appflowy_app/App.tsx | 4 +- .../src/appflowy_app/AppMain.hooks.ts | 10 + .../components/_shared/Button.tsx | 10 +- .../_shared/EditRow/ChangeFieldTypePopup.tsx | 2 +- .../EditRow/CheckList/CheckListOption.tsx | 2 +- .../EditRow/CheckList/EditCheckListPopup.tsx | 8 +- .../_shared/EditRow/Date/DateFormatPopup.tsx | 4 +- .../_shared/EditRow/Date/DateTypeOptions.tsx | 16 +- .../EditRow/Date/NumberFormatPopup.tsx | 4 +- .../_shared/EditRow/Date/TimeFormatPopup.tsx | 8 +- .../_shared/EditRow/EditCellWrapper.tsx | 4 +- .../_shared/EditRow/EditFieldPopup.tsx | 10 +- .../components/_shared/EditRow/EditRow.tsx | 8 +- .../_shared/EditRow/Options/CellOption.tsx | 4 +- .../_shared/EditRow/Options/CellOptions.tsx | 8 +- .../EditRow/Options/CellOptionsPopup.tsx | 2 +- .../EditRow/Options/EditCellOptionPopup.tsx | 8 +- .../EditRow/Options/SelectedOption.tsx | 2 +- .../_shared/EditRow/PropertiesPanel.tsx | 26 +- .../components/_shared/PopupSelect.tsx | 2 +- .../components/_shared/PopupWindow.tsx | 2 +- .../components/_shared/SearchInput.tsx | 2 +- .../components/_shared/getColor.ts | 18 +- .../components/_shared/svg/EditorCheckSvg.tsx | 4 +- .../_shared/svg/EditorUncheckSvg.tsx | 2 +- .../components/_shared/svg/FullView.tsx | 8 +- .../components/_shared/svg/GroupBySvg.tsx | 10 +- .../components/board/BoardCard.tsx | 9 +- .../components/board/BoardFieldsPopup.tsx | 2 +- .../components/board/BoardGroup.tsx | 6 +- .../board/BoardGroupFieldsPopup.tsx | 2 +- .../document/BlockSideToolbar/BlockMenu.tsx | 17 +- .../BlockSideToolbar/BlockMenuTurnInto.tsx | 4 +- .../document/BlockSideToolbar/index.tsx | 6 +- .../document/BlockSlash/BlockSlashMenu.tsx | 5 +- .../components/document/BlockSlash/index.tsx | 3 +- .../document/CalloutBlock/index.tsx | 2 +- .../document/CodeBlock/SelectLanguage.tsx | 9 +- .../components/document/CodeBlock/index.tsx | 5 +- .../document/DocumentTitle/index.tsx | 5 +- .../document/EquationBlock/index.tsx | 9 +- .../document/ImageBlock/EditImage.tsx | 21 +- .../document/ImageBlock/ImageAlign.tsx | 10 +- .../document/ImageBlock/ImagePlaceholder.tsx | 6 +- .../document/ImageBlock/ImageRender.tsx | 2 +- .../document/ImageBlock/ImageToolbar.tsx | 9 +- .../components/document/Node/index.tsx | 7 +- .../document/TextActionMenu/index.tsx | 2 +- .../TextActionMenu/menu/FormatButton.tsx | 32 +- .../TextActionMenu/menu/MenuTooltip.tsx | 16 - .../TextActionMenu/menu/TurnIntoSelect.tsx | 9 +- .../document/TextActionMenu/menu/index.tsx | 2 +- .../components/document/TextBlock/index.tsx | 10 +- .../BlockPopover/BlockPopover.hooks.tsx | 29 +- .../_shared/InlineBlock/InlineContainer.tsx | 1 + .../document/_shared/Message/index.tsx | 18 +- .../document/_shared/SlateEditor/TextLeaf.tsx | 16 +- .../TemporaryInput/EquationEditContent.tsx | 2 +- .../TemporaryInput/TemporaryEquation.tsx | 2 +- .../document/_shared/TemporaryInput/index.tsx | 31 +- .../_shared/TextLink/EditLinkToolbar.tsx | 8 +- .../_shared/TextLink/LinkEditPopover.tsx | 9 +- .../document/_shared/ToolbarTooltip/index.tsx | 16 + .../document/_shared/UploadImage/index.tsx | 52 +++- .../grid/GridAddView/GridAddView.tsx | 2 +- .../grid/GridTableHeader/GridTableHeader.tsx | 6 +- .../GridTableHeader/GridTableHeaderItem.tsx | 4 +- .../grid/GridTableRows/GridTableRow.tsx | 4 +- .../components/layout/FooterPanel.tsx | 2 +- .../layout/HeaderPanel/Breadcrumbs.tsx | 10 +- .../layout/HeaderPanel/HeaderPanel.tsx | 2 +- .../layout/HeaderPanel/LanguageButton.tsx | 16 - .../layout/HeaderPanel/MoreMenu.tsx | 45 +++ .../layout/HeaderPanel/OptionsPopup.tsx | 23 -- .../layout/HeaderPanel/PageOptions.hooks.ts | 21 +- .../layout/HeaderPanel/PageOptions.tsx | 69 ++++- .../layout/NavigationPanel/MoreMenu.tsx | 84 +++++ .../layout/NavigationPanel/NavItem.hooks.ts | 77 ++--- .../layout/NavigationPanel/NavItem.tsx | 183 +++++------ .../NavigationPanel/NavItemOptionsPopup.tsx | 57 ---- .../NavigationPanel/NavigationPanel.tsx | 28 +- .../layout/NavigationPanel/NewPageMenu.tsx | 67 ++++ .../layout/NavigationPanel/NewPagePopup.tsx | 57 ---- .../layout/NavigationPanel/NewViewButton.tsx | 4 +- .../layout/NavigationPanel/RenameDialog.tsx | 52 ++++ .../layout/NavigationPanel/RenamePopup.tsx | 47 --- .../layout/NavigationPanel/TrashButton.tsx | 2 +- .../layout/UserSetting/AppearanceSetting.tsx | 13 +- .../layout/UserSetting/LanguageSetting.tsx | 71 ++++- .../components/layout/UserSetting/Menu.tsx | 13 +- .../layout/UserSetting/SettingPanel.tsx | 8 +- .../components/layout/UserSetting/index.tsx | 13 +- .../components/layout/WorkspaceUser.tsx | 2 +- .../components/tests/ColorPalette.tsx | 24 +- .../src/appflowy_app/i18n/config.ts | 15 + .../src/appflowy_app/interfaces/index.ts | 1 + .../stores/effects/user/user_bd_svc.ts | 8 +- .../effects/user/user_setting_controller.ts | 2 +- .../stores/i18n/initializeI18n.ts | 61 ---- .../src/appflowy_app/vite-env.d.ts | 1 - .../appflowy_tauri/src/styles/Calendar.css | 2 +- .../appflowy_tauri/src/styles/color/dark.css | 21 -- .../src/styles/color/default.css | 54 ---- .../appflowy_tauri/src/styles/color/light.css | 21 -- frontend/appflowy_tauri/src/styles/mui.css | 34 ++- .../appflowy_tauri/src/styles/template.css | 13 +- .../src/styles/variables/dark.variables.css | 117 +++++++ .../src/styles/variables/index.css | 2 + .../src/styles/variables/light.variables.css | 121 ++++++++ .../style-dictionary/config.cjs | 114 +++++++ .../style-dictionary/tailwind/box-shadow.cjs | 9 + .../style-dictionary/tailwind/colors.cjs | 70 +++++ .../style-dictionary/tokens/base.json | 286 ++++++++++++++++++ .../style-dictionary/tokens/dark.json | 207 +++++++++++++ .../style-dictionary/tokens/light.json | 223 ++++++++++++++ frontend/appflowy_tauri/tailwind.config.cjs | 69 +---- frontend/resources/translations/en.json | 70 ++++- 123 files changed, 2455 insertions(+), 937 deletions(-) create mode 100644 frontend/appflowy_tauri/scripts/i18n/index.cjs create mode 100644 frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts create mode 100644 frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx create mode 100644 frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts delete mode 100644 frontend/appflowy_tauri/src/appflowy_app/stores/i18n/initializeI18n.ts delete mode 100644 frontend/appflowy_tauri/src/styles/color/dark.css delete mode 100644 frontend/appflowy_tauri/src/styles/color/default.css delete mode 100644 frontend/appflowy_tauri/src/styles/color/light.css create mode 100644 frontend/appflowy_tauri/src/styles/variables/dark.variables.css create mode 100644 frontend/appflowy_tauri/src/styles/variables/index.css create mode 100644 frontend/appflowy_tauri/src/styles/variables/light.variables.css create mode 100644 frontend/appflowy_tauri/style-dictionary/config.cjs create mode 100644 frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs create mode 100644 frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs create mode 100644 frontend/appflowy_tauri/style-dictionary/tokens/base.json create mode 100644 frontend/appflowy_tauri/style-dictionary/tokens/dark.json create mode 100644 frontend/appflowy_tauri/style-dictionary/tokens/light.json diff --git a/frontend/appflowy_tauri/.gitignore b/frontend/appflowy_tauri/.gitignore index 0ae4944041..c69d65a4d3 100644 --- a/frontend/appflowy_tauri/.gitignore +++ b/frontend/appflowy_tauri/.gitignore @@ -24,4 +24,5 @@ dist-ssr *.sw? **/src/services/backend/models/ -**/src/services/backend/events/ \ No newline at end of file +**/src/services/backend/events/ +**/src/appflowy_app/i18n/translations/ \ No newline at end of file diff --git a/frontend/appflowy_tauri/package.json b/frontend/appflowy_tauri/package.json index a6befd4733..52f0d9abfd 100644 --- a/frontend/appflowy_tauri/package.json +++ b/frontend/appflowy_tauri/package.json @@ -5,14 +5,16 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "pnpm sync:i18n && tsc && vite build", "preview": "vite preview", "format": "prettier --write .", "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .", - "test:errors": "tsc --noEmit && eslint --quiet --ext .js,.ts,.tsx .", - "test:prettier": "yarn prettier --list-different src", + "test:errors": "pnpm sync:i18n && tsc --noEmit && eslint --quiet --ext .js,.ts,.tsx .", + "test:prettier": "pnpm prettier --list-different src", "tauri:clean": "cargo make --cwd .. tauri_clean", - "tauri:dev": "tauri dev" + "tauri:dev": "pnpm sync:i18n && tauri dev", + "sync:i18n": "node scripts/i18n/index.cjs", + "css:variables": "node style-dictionary/config.cjs" }, "dependencies": { "@emoji-mart/data": "^1.1.2", @@ -25,13 +27,14 @@ "@slate-yjs/core": "^1.0.0", "@tanstack/react-virtual": "3.0.0-beta.54", "@tauri-apps/api": "^1.2.0", - "dayjs": "^1.11.7", + "dayjs": "^1.11.9", "emoji-mart": "^5.5.2", "emoji-regex": "^10.2.1", "events": "^3.3.0", "google-protobuf": "^3.21.2", "i18next": "^22.4.10", "i18next-browser-languagedetector": "^7.0.1", + "i18next-resources-to-backend": "^1.1.4", "is-hotkey": "^0.2.0", "jest": "^29.5.0", "katex": "^0.16.7", @@ -56,7 +59,6 @@ "slate-react": "^0.94.2", "ts-results": "^3.3.0", "utf8": "^3.0.0", - "y-indexeddb": "^9.0.9", "yjs": "^13.5.51" }, "devDependencies": { @@ -83,6 +85,7 @@ "postcss": "^8.4.21", "prettier": "2.8.4", "prettier-plugin-tailwindcss": "^0.2.2", + "style-dictionary": "^3.8.0", "tailwindcss": "^3.2.7", "typescript": "^4.6.4", "uuid": "^9.0.0", diff --git a/frontend/appflowy_tauri/pnpm-lock.yaml b/frontend/appflowy_tauri/pnpm-lock.yaml index 043a661188..948b9ae693 100644 --- a/frontend/appflowy_tauri/pnpm-lock.yaml +++ b/frontend/appflowy_tauri/pnpm-lock.yaml @@ -32,8 +32,8 @@ dependencies: specifier: ^1.2.0 version: 1.3.0 dayjs: - specifier: ^1.11.7 - version: 1.11.7 + specifier: ^1.11.9 + version: 1.11.9 emoji-mart: specifier: ^5.5.2 version: 5.5.2 @@ -52,6 +52,9 @@ dependencies: i18next-browser-languagedetector: specifier: ^7.0.1 version: 7.0.1 + i18next-resources-to-backend: + specifier: ^1.1.4 + version: 1.1.4 is-hotkey: specifier: ^0.2.0 version: 0.2.0 @@ -124,9 +127,6 @@ dependencies: utf8: specifier: ^3.0.0 version: 3.0.0 - y-indexeddb: - specifier: ^9.0.9 - version: 9.0.11(yjs@13.6.1) yjs: specifier: ^13.5.51 version: 13.6.1 @@ -201,6 +201,9 @@ devDependencies: prettier-plugin-tailwindcss: specifier: ^0.2.2 version: 0.2.8(prettier@2.8.4) + style-dictionary: + specifier: ^3.8.0 + version: 3.8.0 tailwindcss: specifier: ^3.2.7 version: 3.3.2 @@ -2193,6 +2196,13 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + /camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + dependencies: + pascal-case: 3.1.2 + tslib: 2.5.0 + dev: true + /camelcase-css@2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} @@ -2211,6 +2221,14 @@ packages: /caniuse-lite@1.0.30001487: resolution: {integrity: sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==} + /capital-case@1.0.4: + resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + upper-case-first: 2.0.2 + dev: true + /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2226,6 +2244,23 @@ packages: ansi-styles: 4.3.0 supports-color: 7.2.0 + /change-case@4.1.2: + resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} + dependencies: + camel-case: 4.1.2 + capital-case: 1.0.4 + constant-case: 3.0.4 + dot-case: 3.0.4 + header-case: 2.0.4 + no-case: 3.0.4 + param-case: 3.0.4 + pascal-case: 3.1.2 + path-case: 3.0.4 + sentence-case: 3.0.4 + snake-case: 3.0.4 + tslib: 2.5.0 + dev: true + /char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -2308,7 +2343,6 @@ packages: /commander@8.3.0: resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} engines: {node: '>= 12'} - dev: false /compute-scroll-into-view@1.0.20: resolution: {integrity: sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==} @@ -2317,6 +2351,14 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /constant-case@3.0.4: + resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + upper-case: 2.0.2 + dev: true + /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -2358,8 +2400,8 @@ packages: /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - /dayjs@1.11.7: - resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + /dayjs@1.11.9: + resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==} dev: false /debug@4.3.4: @@ -2455,6 +2497,13 @@ packages: csstype: 3.1.2 dev: false + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + dev: true + /electron-to-chromium@1.4.394: resolution: {integrity: sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==} @@ -2884,6 +2933,15 @@ packages: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true + /fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.0 + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -3029,7 +3087,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: false /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} @@ -3072,6 +3129,13 @@ packages: dependencies: function-bind: 1.1.1 + /header-case@2.0.4: + resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} + dependencies: + capital-case: 1.0.4 + tslib: 2.5.0 + dev: true + /hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: @@ -3099,6 +3163,12 @@ packages: '@babel/runtime': 7.21.5 dev: false + /i18next-resources-to-backend@1.1.4: + resolution: {integrity: sha512-hMyr9AOmIea17AOaVe1srNxK/l3mbk81P7Uf3fdcjlw3ehZy3UNTd0OP3EEi6yu4J02kf9jzhCcjokz6AFlEOg==} + dependencies: + '@babel/runtime': 7.21.5 + dev: false + /i18next@22.4.15: resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==} dependencies: @@ -3825,6 +3895,18 @@ packages: engines: {node: '>=6'} hasBin: true + /jsonc-parser@3.2.0: + resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + dev: true + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.0 + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /jsx-ast-utils@3.3.3: resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} engines: {node: '>=4.0'} @@ -3904,7 +3986,6 @@ packages: /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} @@ -3912,6 +3993,12 @@ packages: dependencies: js-tokens: 4.0.0 + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.5.0 + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -4003,6 +4090,13 @@ packages: /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.5.0 + dev: true + /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} dev: false @@ -4151,6 +4245,13 @@ packages: engines: {node: '>=6'} dev: false + /param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + dependencies: + dot-case: 3.0.4 + tslib: 2.5.0 + dev: true + /parchment@1.1.4: resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} @@ -4170,6 +4271,20 @@ packages: lines-and-columns: 1.2.4 dev: false + /pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + dev: true + + /path-case@3.0.4: + resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} + dependencies: + dot-case: 3.0.4 + tslib: 2.5.0 + dev: true + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4799,6 +4914,14 @@ packages: dependencies: lru-cache: 6.0.0 + /sentence-case@3.0.4: + resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} + dependencies: + no-case: 3.0.4 + tslib: 2.5.0 + upper-case-first: 2.0.2 + dev: true + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4858,6 +4981,13 @@ packages: tiny-warning: 1.0.3 dev: false + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + dependencies: + dot-case: 3.0.4 + tslib: 2.5.0 + dev: true + /source-map-js@1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -4966,6 +5096,22 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + /style-dictionary@3.8.0: + resolution: {integrity: sha512-wHlB/f5eO3mDcYv6WtOz6gvQC477jBKrwuIXe+PtHskTCBsJdAOvL8hCquczJxDui2TnwpeNE+2msK91JJomZg==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + change-case: 4.1.2 + commander: 8.3.0 + fs-extra: 10.1.0 + glob: 7.2.3 + json5: 2.2.3 + jsonc-parser: 3.2.0 + lodash: 4.17.21 + tinycolor2: 1.6.0 + dev: true + /stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} dev: false @@ -5077,6 +5223,10 @@ packages: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} dev: false + /tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + dev: true + /tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: false @@ -5105,7 +5255,6 @@ packages: /tslib@2.5.0: resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - dev: false /tsutils@3.21.0(typescript@4.9.5): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -5161,6 +5310,11 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /universalify@2.0.0: + resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} + engines: {node: '>= 10.0.0'} + dev: true + /update-browserslist-db@1.0.11(browserslist@4.21.5): resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true @@ -5171,6 +5325,18 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 + /upper-case-first@2.0.2: + resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} + dependencies: + tslib: 2.5.0 + dev: true + + /upper-case@2.0.2: + resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} + dependencies: + tslib: 2.5.0 + dev: true + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -5313,15 +5479,6 @@ packages: signal-exit: 3.0.7 dev: false - /y-indexeddb@9.0.11(yjs@13.6.1): - resolution: {integrity: sha512-HOKQ70qW1h2WJGtOKu9rE8fbX86ExVZedecndMuhwax3yM4DQsQzCTGHt/jvTrFZr/9Ahvd8neD6aZ4dMMjtdg==} - peerDependencies: - yjs: ^13.0.0 - dependencies: - lib0: 0.2.74 - yjs: 13.6.1 - dev: false - /y-protocols@1.0.5: resolution: {integrity: sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==} dependencies: diff --git a/frontend/appflowy_tauri/scripts/i18n/index.cjs b/frontend/appflowy_tauri/scripts/i18n/index.cjs new file mode 100644 index 0000000000..2b5fcfcf4a --- /dev/null +++ b/frontend/appflowy_tauri/scripts/i18n/index.cjs @@ -0,0 +1,53 @@ +const languages = [ + 'ar-SA', + 'ca-ES', + 'de-DE', + 'en', + 'es-VE', + 'eu-ES', + 'fr-FR', + 'hu-HU', + 'id-ID', + 'it-IT', + 'ja-JP', + 'ko-KR', + 'pl-PL', + 'pt-BR', + 'pt-PT', + 'ru-RU', + 'sv', + 'tr-TR', + 'zh-CN', + 'zh-TW', +]; + +const fs = require('fs'); +languages.forEach(language => { + const json = require(`../../../resources/translations/${language}.json`); + const outputJSON = flattenJSON(json); + const output = JSON.stringify(outputJSON); + const isExistDir = fs.existsSync('./src/appflowy_app/i18n/translations'); + if (!isExistDir) { + fs.mkdirSync('./src/appflowy_app/i18n/translations'); + } + fs.writeFile(`./src/appflowy_app/i18n/translations/${language}.json`, new Uint8Array(Buffer.from(output)), (res) => { + if (res) { + console.error(res); + } + }) +}) +function flattenJSON(obj, prefix = '') { + let result = {}; + + for (let key in obj) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + const nestedKeys = flattenJSON(obj[key], `${prefix}${key}.`); + result = { ...result, ...nestedKeys }; + } else { + result[`${prefix}${key}`] = obj[key]; + } + } + + return result; +} + diff --git a/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts b/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts new file mode 100644 index 0000000000..6adbb4a512 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts @@ -0,0 +1,8 @@ +import resources from './resources'; + +declare module 'i18next' { + interface CustomTypeOptions { + defaultNS: 'translation'; + resources: typeof resources; + } +} diff --git a/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts b/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts new file mode 100644 index 0000000000..479f05f013 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts @@ -0,0 +1,7 @@ +import translation from '$app/i18n/translations/en.json'; + +const resources = { + translation, +} as const; + +export default resources; diff --git a/frontend/appflowy_tauri/src/appflowy_app/App.tsx b/frontend/appflowy_tauri/src/appflowy_app/App.tsx index 36f2d64e63..9381737341 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/App.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/App.tsx @@ -4,14 +4,12 @@ import { Provider } from 'react-redux'; import { store } from './stores/store'; import { ErrorHandlerPage } from './components/error/ErrorHandlerPage'; -import initializeI18n from './stores/i18n/initializeI18n'; +import '$app/i18n/config'; import { ErrorBoundary } from 'react-error-boundary'; import AppMain from '$app/AppMain'; -initializeI18n(); - const App = () => { return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts index 4d1b3d3f17..fd8c411ba6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts @@ -19,10 +19,20 @@ export function useUserSetting() { useEffect(() => { userSettingController?.getAppearanceSetting().then((res) => { if (!res) return; + const locale = res.locale; + let language = 'en'; + + if (locale.language_code && locale.country_code) { + language = `${locale.language_code}-${locale.country_code}`; + } else if (locale.language_code) { + language = locale.language_code; + } + dispatch( currentUserActions.setUserSetting({ themeMode: res.theme_mode, theme: res.theme as Theme, + language: language, }) ); }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx index fb6783bc4a..615bc1fdf5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/Button.tsx @@ -14,23 +14,23 @@ export const Button = ({ useEffect(() => { switch (size) { case 'primary': - setCls('w-[340px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill'); + setCls('w-[340px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill'); break; case 'medium': - setCls('w-[170px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill'); + setCls('w-[170px] h-[48px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill'); break; case 'small': setCls( - 'w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-content-default text-content-onfill text-xs hover:bg-content-hover' + 'w-[68px] h-[32px] flex items-center justify-center rounded-lg bg-content-default text-content-on-fill text-xs hover:bg-content-hover' ); break; case 'medium-transparent': setCls( - 'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-content-default text-content-default transition-colors duration-300 hover:bg-content-hover hover:text-content-onfill' + 'w-[170px] h-[48px] flex items-center justify-center rounded-lg border border-content-default text-content-default transition-colors duration-300 hover:bg-content-blue-50 hover:text-content-on-fill' ); break; case 'box-small-transparent': - setCls('text-icon-default w-[24px] h-[24px] rounded hover:bg-fill-hover'); + setCls('text-icon-default w-[24px] h-[24px] rounded hover:bg-fill-list-hover'); break; } }, [size]); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx index 78ad14cee2..ffac4ec2b1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/EditRow/ChangeFieldTypePopup.tsx @@ -32,7 +32,7 @@ export const ChangeFieldTypePopup = ({ -
+
-
+
setShowAdvancedProperties(!showAdvancedProperties)} - className={'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-hover'} + className={ + 'flex cursor-pointer items-center justify-between gap-8 rounded-lg px-2 py-2 hover:bg-fill-list-active' + } >
Advanced Properties
@@ -187,19 +191,25 @@ export const PropertiesPanel = ({
{showAdvancedProperties && (
- - - -
@@ -86,7 +86,7 @@ export const BoardGroup = ({
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx index b3b07e8a5a..223ad76056 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageAlign.tsx @@ -4,8 +4,9 @@ import { useSubscribeDocument } from '$app/components/document/_shared/Subscribe import { Align } from '$app/interfaces/document'; import { FormatAlignCenter, FormatAlignLeft, FormatAlignRight } from '@mui/icons-material'; import { updateNodeDataThunk } from '$app_reducers/document/async-actions'; -import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip'; +import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip'; import Popover from '@mui/material/Popover'; +import { useTranslation } from 'react-i18next'; function ImageAlign({ id, @@ -21,6 +22,7 @@ function ImageAlign({ const ref = useRef(null); const [anchorEl, setAnchorEl] = useState(); const popoverOpen = Boolean(anchorEl); + const { t } = useTranslation(); useEffect(() => { if (popoverOpen) { @@ -61,7 +63,7 @@ function ImageAlign({ return ( <> - +
{renderAlign(align)}
-
+ setAnchorEl(undefined)} PaperProps={{ style: { - backgroundColor: 'var(--color-bg-body)', + backgroundColor: 'var(--bg-body)', }, }} > diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx index acb523fdf4..0f311216f6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImagePlaceholder.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Alert, CircularProgress } from '@mui/material'; import { ImageSvg } from '$app/components/_shared/svg/ImageSvg'; +import { useTranslation } from 'react-i18next'; function ImagePlaceholder({ error, @@ -20,6 +21,7 @@ function ImagePlaceholder({ openPopover: () => void; }) { const visible = loading || error || isEmpty; + const { t } = useTranslation(); return (
- Add an image + {t('document.imageBlock.placeholder')}
)}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx index 0148aa8a7e..074c004526 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageRender.tsx @@ -29,7 +29,7 @@ function ImageRender({ } top-0 flex h-[100%] w-[15px] cursor-col-resize items-center justify-center`} >
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx index 1bbe41ea18..a449c85509 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/ImageBlock/ImageToolbar.tsx @@ -1,11 +1,12 @@ import React, { useState } from 'react'; import { Align } from '$app/interfaces/document'; import ImageAlign from '$app/components/document/ImageBlock/ImageAlign'; -import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip'; +import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip'; import { DeleteOutline } from '@mui/icons-material'; import { useAppDispatch } from '$app/stores/store'; import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks'; import { deleteNodeThunk } from '$app_reducers/document/async-actions'; +import { useTranslation } from 'react-i18next'; function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: Align }) { const [popoverOpen, setPopoverOpen] = useState(false); @@ -13,6 +14,8 @@ function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: A const dispatch = useAppDispatch(); const { controller } = useSubscribeDocument(); + const { t } = useTranslation(); + return ( <>
setPopoverOpen(true)} onClose={() => setPopoverOpen(false)} /> - +
{ dispatch(deleteNodeThunk({ id, controller })); @@ -30,7 +33,7 @@ function ImageToolbar({ id, open, align }: { id: string; open: boolean; align: A >
-
+
); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx index 510530021a..926714f02a 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/Node/index.tsx @@ -19,6 +19,7 @@ import CodeBlock from '$app/components/document/CodeBlock'; import { NodeIdContext } from '$app/components/document/_shared/SubscribeNode.hooks'; import EquationBlock from '$app/components/document/EquationBlock'; import ImageBlock from '$app/components/document/ImageBlock'; +import { useTranslation } from 'react-i18next'; function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes) { const { node, childIds, isSelected, ref } = useNode(id); @@ -82,7 +83,7 @@ function NodeComponent({ id, ...props }: { id: string } & React.HTMLAttributes {isSelected ? ( -
+
) : null}
@@ -94,9 +95,11 @@ const NodeWithErrorBoundary = withErrorBoundary(NodeComponent, { }); const UnSupportedBlock = () => { + const { t } = useTranslation(); + return ( -

The current version does not support this Block.

+

{t('unSupportBlock')}

); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx index 0694e35417..cc6376784f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/index.tsx @@ -18,7 +18,7 @@ const TextActionComponent = ({ container }: { container: HTMLDivElement }) => { style={{ opacity: 0, }} - className='absolute mt-[-6px] inline-flex h-[32px] min-w-[100px] items-stretch overflow-hidden rounded-[8px] bg-bg-base leading-tight text-text-title shadow-lg transition-opacity duration-100' + className='absolute mt-[-6px] inline-flex h-[32px] min-w-[100px] items-stretch overflow-hidden rounded-[8px] bg-fill-toolbar leading-tight text-content-on-fill shadow-md transition-opacity duration-100' onMouseDown={(e) => { // prevent toolbar from taking focus away from editor e.preventDefault(); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx index 1ab61e2e11..4633129a79 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/FormatButton.tsx @@ -1,7 +1,6 @@ -import IconButton from '@mui/material/IconButton'; import React, { useCallback, useEffect, useMemo } from 'react'; import { TemporaryType, TextAction } from '$app/interfaces/document'; -import MenuTooltip from '$app/components/document/TextActionMenu/menu/MenuTooltip'; +import ToolbarTooltip from '$app/components/document/_shared/ToolbarTooltip'; import { getFormatActiveThunk, toggleFormatThunk } from '$app_reducers/document/async-actions/format'; import { useAppDispatch, useAppSelector } from '$app/stores/store'; import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; @@ -18,18 +17,19 @@ import { StrikethroughSOutlined, } from '@mui/icons-material'; import LinkIcon from '@mui/icons-material/AddLink'; +import { useTranslation } from 'react-i18next'; export const iconSize = { width: 18, height: 18 }; const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => { const dispatch = useAppDispatch(); const { docId, controller } = useSubscribeDocument(); - + const { t } = useTranslation(); const focusId = useAppSelector((state) => state[RANGE_NAME][docId]?.focus?.id || ''); const { node: focusNode } = useSubscribeNode(focusId); const [isActive, setIsActive] = React.useState(false); - const color = useMemo(() => (isActive ? 'text-content-hover' : 'text-text-title'), [isActive]); + const color = useMemo(() => (isActive ? 'text-content-on-fill-hover' : ''), [isActive]); const isFormatActive = useCallback(async () => { if (!focusNode) return false; @@ -82,15 +82,15 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => const formatTooltips: Record = useMemo( () => ({ - [TextAction.Bold]: 'Bold', - [TextAction.Italic]: 'Italic', - [TextAction.Underline]: 'Underline', - [TextAction.Strikethrough]: 'Strike through', - [TextAction.Code]: 'Mark as Code', - [TextAction.Link]: 'Add Link', - [TextAction.Equation]: 'Create equation', + [TextAction.Bold]: t('toolbar.bold'), + [TextAction.Italic]: t('toolbar.italic'), + [TextAction.Underline]: t('toolbar.underline'), + [TextAction.Strikethrough]: t('toolbar.strike'), + [TextAction.Code]: t('toolbar.inlineCode'), + [TextAction.Link]: t('toolbar.addLink'), + [TextAction.Equation]: t('document.plugins.mathEquation.addMathEquation'), }), - [] + [t] ); const formatClick = useCallback( @@ -132,7 +132,7 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => marginRight: '0.25rem', }} /> -
Link
+
{t('toolbar.link')}
); case TextAction.Equation: @@ -140,14 +140,14 @@ const FormatButton = ({ format, icon }: { format: TextAction; icon: string }) => default: return null; } - }, [icon]); + }, [icon, t]); return ( - +
formatClick(format)}> {formatIcon}
-
+ ); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx deleted file mode 100644 index d1978f30f1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/MenuTooltip.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import Tooltip from '@mui/material/Tooltip'; - -function MenuTooltip({ title, children }: { children: JSX.Element; title?: string }) { - return ( - -
{children}
-
- ); -} - -export default MenuTooltip; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx index 648ae7a451..2e9e91fdf4 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextActionMenu/menu/TurnIntoSelect.tsx @@ -1,9 +1,9 @@ import React, { useCallback } from 'react'; import TurnIntoPopover from '$app/components/document/_shared/TurnInto'; -import Button from '@mui/material/Button'; import ArrowDropDown from '@mui/icons-material/ArrowDropDown'; -import MenuTooltip from './MenuTooltip'; import { useSubscribeNode } from '$app/components/document/_shared/SubscribeNode.hooks'; +import { useTranslation } from 'react-i18next'; +import ToolbarTooltip from '../../_shared/ToolbarTooltip'; function TurnIntoSelect({ id }: { id: string }) { const [anchorPosition, setAnchorPosition] = React.useState<{ @@ -26,15 +26,16 @@ function TurnIntoSelect({ id }: { id: string }) { }, []); const open = Boolean(anchorPosition); + const { t } = useTranslation(); return ( <> - +
{node.type}
-
+ group.length > 0 && ( -
+
{group.map((item) => (
{renderNode(item)} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx index 88efbbaf20..2f5a57a78d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/TextBlock/index.tsx @@ -5,6 +5,7 @@ import { useChange } from '$app/components/document/_shared/EditorHooks/useChang import NodeChildren from '$app/components/document/Node/NodeChildren'; import { useKeyDown } from '$app/components/document/TextBlock/useKeyDown'; import { useSelection } from '$app/components/document/_shared/EditorHooks/useSelection'; +import { useTranslation } from 'react-i18next'; interface Props { node: NestedBlock; @@ -15,10 +16,17 @@ function TextBlock({ node, childIds, placeholder }: Props) { const { value, onChange } = useChange(node); const selectionProps = useSelection(node.id); const { onKeyDown } = useKeyDown(node.id); + const { t } = useTranslation(); return ( <> - + ); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx index e38006bed9..81c9cd0e4c 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/BlockPopover/BlockPopover.hooks.tsx @@ -17,15 +17,18 @@ export function useBlockPopover({ onAfterOpen?: () => void; renderContent: ({ onClose }: { onClose: () => void }) => React.ReactNode; }) { - const anchorElRef = useRef(null); + const anchorElRef = useRef(null); const { docId } = useSubscribeDocument(); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); + const [anchorPosition, setAnchorPosition] = useState<{ + top: number; + left: number; + }>(); + const open = Boolean(anchorPosition); const editing = useEditingState(id); const dispatch = useAppDispatch(); const closePopover = useCallback(() => { - setAnchorEl(null); + setAnchorPosition(undefined); dispatch( blockEditActions.setBlockEditState({ id: docId, @@ -48,7 +51,14 @@ export function useBlockPopover({ }, [dispatch, docId, id]); const openPopover = useCallback(() => { - setAnchorEl(anchorElRef.current); + if (!anchorElRef.current) return; + + const rect = anchorElRef.current.getBoundingClientRect(); + + setAnchorPosition({ + top: rect.top + rect.height, + left: rect.left + rect.width / 2, + }); selectBlock(); onAfterOpen?.(); }, [onAfterOpen, selectBlock]); @@ -68,21 +78,18 @@ export function useBlockPopover({ vertical: 'top', horizontal: 'center', }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'center', - }} onMouseDown={(e) => e.stopPropagation()} onClose={closePopover} open={open} - anchorEl={anchorEl} + anchorReference={'anchorPosition'} + anchorPosition={anchorPosition} > {renderContent({ onClose: closePopover, })} ); - }, [anchorEl, closePopover, open, renderContent]); + }, [anchorPosition, closePopover, open, renderContent]); useEffect(() => { if (!anchorElRef.current) { diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx index b3aaa9fbda..44fa66cc32 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/InlineBlock/InlineContainer.tsx @@ -132,6 +132,7 @@ function InlineContainer({ style={{ pointerEvents: 'none', }} + className={'inline-block-content'} > {renderNode()} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx index 99eac15767..69e9b74031 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/Message/index.tsx @@ -1,6 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react'; -import { Portal, Snackbar } from '@mui/material'; -import { TransitionProps } from '@mui/material/transitions'; +import { Alert, Portal, Snackbar } from '@mui/material'; import Slide, { SlideProps } from '@mui/material/Slide'; function SlideTransition(props: SlideProps) { @@ -11,6 +10,7 @@ interface MessageProps { message?: string; key?: string; duration?: number; + type?: 'success' | 'error'; } export function useMessage() { const [state, setState] = useState(); @@ -23,6 +23,7 @@ export function useMessage() { const contentHolder = useMemo(() => { const open = !!state; + return ( + > + <> + {state?.type ? ( + + {state.message} + + ) : ( + {state?.message} + )} + + ); }, [hide, state]); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx index b4ea311565..2844ae56b4 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/SlateEditor/TextLeaf.tsx @@ -37,10 +37,10 @@ const TextLeaf = (props: TextLeafProps) => { }; let newChildren = children; - if (leaf.code) { + if (leaf.code && !leaf.temporary) { newChildren = ( { isCodeBlock && 'token', leaf.prism_token && leaf.prism_token, leaf.strikethrough && 'line-through', - leaf.selection_high_lighted && 'bg-fill-selector', - leaf.link_selection_lighted && 'text-text-link-selector bg-fill-selector', - leaf.code && 'inline-code', + leaf.selection_high_lighted && 'bg-content-blue-100', + leaf.link_selection_lighted && 'text-text-link-selector bg-content-blue-100', + leaf.code && !leaf.temporary && 'inline-code', leaf.bold && 'font-bold', leaf.italic && 'italic', leaf.underline && 'underline', @@ -114,7 +114,11 @@ const TextLeaf = (props: TextLeafProps) => { } if (leaf.temporary) { - newChildren = {newChildren}; + newChildren = ( + + {newChildren} + + ); } return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx index 71a74fbb13..24c56fa073 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/EquationEditContent.tsx @@ -1,7 +1,7 @@ import React from 'react'; import TextField from '@mui/material/TextField'; import { CheckOutlined, FunctionsOutlined } from '@mui/icons-material'; -import { Divider, IconButton, InputAdornment } from '@mui/material'; +import { IconButton, InputAdornment } from '@mui/material'; function EquationEditContent({ value, diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx index 53332f31a2..061a3bb5af 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/TemporaryEquation.tsx @@ -4,7 +4,7 @@ import KatexMath from '$app/components/document/_shared/KatexMath'; function TemporaryEquation({ latex }: { latex: string }) { return ( - + {latex ? ( ) : ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx index a93eb5c9dd..b72bc1c8ff 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TemporaryInput/index.tsx @@ -1,33 +1,36 @@ import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { TemporaryType } from '$app/interfaces/document'; +import { RangeStaticNoId, TemporaryType } from '$app/interfaces/document'; import TemporaryEquation from '$app/components/document/_shared/TemporaryInput/TemporaryEquation'; import { useSubscribeTemporary } from '$app/components/document/_shared/SubscribeTemporary.hooks'; -import { isOverlappingPrefix } from '$app/utils/document/temporary'; import { PopoverPosition } from '@mui/material'; import { useAppDispatch } from '$app/stores/store'; import { temporaryActions } from '$app_reducers/document/temporary_slice'; import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks'; -function TemporaryInput({ leaf, children }: { leaf: { text: string }; children: React.ReactNode }) { +function TemporaryInput({ + leaf, + children, + getSelection, +}: { + leaf: { text: string }; + children: React.ReactNode; + getSelection: (node: Element) => RangeStaticNoId | null; +}) { const temporaryState = useSubscribeTemporary(); const id = temporaryState?.id; const dispatch = useAppDispatch(); const ref = useRef(null); const { docId } = useSubscribeDocument(); const match = useMemo(() => { + if (!ref.current) return false; if (!leaf.text) return false; if (!temporaryState) return false; - const { selectedText, type } = temporaryState; + const { selectedText } = temporaryState; + const selection = getSelection(ref.current); - switch (type) { - case TemporaryType.Equation: - // when the leaf is split, the placeholder is not the same as the leaf text, - // so we can only check for overlapping prefix and hidden other leafs - return leaf.text === selectedText || isOverlappingPrefix(leaf.text, selectedText); - default: - return false; - } - }, [temporaryState, leaf.text]); + if (!selection) return false; + return leaf.text === selectedText || selection.index <= temporaryState.selection.index; + }, [leaf.text, temporaryState, getSelection]); const renderPlaceholder = useCallback(() => { if (!temporaryState) return null; @@ -69,7 +72,7 @@ function TemporaryInput({ leaf, children }: { leaf: { text: string }; children: return ( {match ? renderPlaceholder() : null} - {children} + {children} ); } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx index f6eb7d62c3..1fb8767c14 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/EditLinkToolbar.tsx @@ -5,6 +5,7 @@ import LanguageIcon from '@mui/icons-material/Language'; import CopyIcon from '@mui/icons-material/CopyAll'; import { copyText } from '$app/utils/document/copy_paste'; import { useMessage } from '$app/components/document/_shared/Message'; +import { useTranslation } from 'react-i18next'; const iconSize = { width: '1rem', @@ -28,6 +29,7 @@ function EditLinkToolbar({ editing: boolean; onEdit: () => void; }) { + const { t } = useTranslation(); const { show, contentHolder } = useMessage(); const ref = useRef(null); @@ -70,9 +72,9 @@ function EditLinkToolbar({ onClick={async () => { try { await copyText(href); - show({ message: 'Copied!', duration: 6000 }); + show({ message: t('message.copy.success'), duration: 6000 }); } catch { - show({ message: 'Copy failed!', duration: 6000 }); + show({ message: t('message.copy.fail'), duration: 6000 }); } }} className={'mr-2 cursor-pointer'} @@ -80,7 +82,7 @@ function EditLinkToolbar({
- Edit + {t('button.edit')}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx index c10900c156..22017da46d 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/TextLink/LinkEditPopover.tsx @@ -8,11 +8,12 @@ import { formatLinkThunk } from '$app_reducers/document/async-actions/link'; import { useSubscribeDocument } from '$app/components/document/_shared/SubscribeDoc.hooks'; import { useSubscribeLinkPopover } from '$app/components/document/_shared/SubscribeLinkPopover.hooks'; import Button from '@mui/material/Button'; +import { useTranslation } from 'react-i18next'; function LinkEditPopover() { const dispatch = useAppDispatch(); const { docId, controller } = useSubscribeDocument(); - + const { t } = useTranslation(); const popoverState = useSubscribeLinkPopover(); const { anchorPosition, id, selection, title = '', href = '', open = false } = popoverState; @@ -101,7 +102,7 @@ function LinkEditPopover() { >
{ onChange({ @@ -111,7 +112,7 @@ function LinkEditPopover() { }} /> onChange({ @@ -123,7 +124,7 @@ function LinkEditPopover() {
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx new file mode 100644 index 0000000000..11323f2a57 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/ToolbarTooltip/index.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import Tooltip from '@mui/material/Tooltip'; + +function ToolbarTooltip({ title, children }: { children: JSX.Element; title?: string }) { + return ( + +
{children}
+
+ ); +} + +export default ToolbarTooltip; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx index 1a7e1242c6..ecb14d7d2f 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/document/_shared/UploadImage/index.tsx @@ -1,30 +1,53 @@ -import React, { useCallback, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { ImageSvg } from '$app/components/_shared/svg/ImageSvg'; import { CircularProgress } from '@mui/material'; import { writeImage } from '$app/utils/document/image'; -import { isTauri } from '$app/utils/env'; +import { useTranslation } from 'react-i18next'; +import { useMessage } from '$app/components/document/_shared/Message'; export interface UploadImageProps { onChange: (filePath: string) => void; } function UploadImage({ onChange }: UploadImageProps) { + const { t } = useTranslation(); + const message = useMessage(); + const inputRef = useRef(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); - const beforeUpload = useCallback((file: File) => { - // check file size and type - const sizeMatched = file.size / 1024 / 1024 < 5; // 5MB - const typeMatched = /image\/(png|jpg|jpeg|gif)/.test(file.type); // png, jpg, jpeg, gif + const beforeUpload = useCallback( + (file: File) => { + // check file size and type + const sizeMatched = file.size / 1024 / 1024 < 5; // 5MB + const typeMatched = /image\/(png|jpg|jpeg|gif)/.test(file.type); // png, jpg, jpeg, gif - return sizeMatched && typeMatched; - }, []); + if (!sizeMatched) { + setError(t('document.imageBlock.error.invalidImageSize')); + } + + if (!typeMatched) { + setError(t('document.imageBlock.error.invalidImageFormat')); + } + + return sizeMatched && typeMatched; + }, + [t] + ); + + useEffect(() => { + if (!error) return; + message.show({ + message: error, + duration: 3000, + type: 'error', + }); + }, [error]); const handleUpload = useCallback( async (file: File) => { if (!file) return; if (!beforeUpload(file)) { - setError('Image should be less than 5MB and in png, jpg, jpeg, gif format'); return; } @@ -38,10 +61,10 @@ function UploadImage({ onChange }: UploadImageProps) { onChange(filePath); } catch { setLoading(false); - setError('Upload failed'); + setError(t('document.imageBlock.error.invalidImage')); } }, - [beforeUpload, onChange] + [beforeUpload, onChange, t] ); const handleChange = useCallback( @@ -88,7 +111,7 @@ function UploadImage({ onChange }: UploadImageProps) {
-
{isTauri() ? 'Click space to chose image' : 'Chose image or drag to space'}
+
{t('document.imageBlock.upload.placeholder')}
{loading ? : null} @@ -112,8 +135,9 @@ function UploadImage({ onChange }: UploadImageProps) { }} className={`mt-5 text-sm text-text-caption`} > - The maximum file size is 5MB. Supported formats: JPG, PNG, GIF, SVG. + {t('document.imageBlock.support')}
+ {message.contentHolder}
); } diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx index e34fd72766..6a28c3f4a8 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/grid/GridAddView/GridAddView.tsx @@ -2,7 +2,7 @@ import AddSvg from '../../_shared/svg/AddSvg'; export const GridAddView = () => { return ( - +
); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx index 1c0cb5e7b9..959532b1fa 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/Breadcrumbs.tsx @@ -38,10 +38,16 @@ export const Breadcrumbs = ({ menuHidden, onShowMenuClick }: { menuHidden: boole )} - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx index 1dbeb8b125..42f7d04af5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/HeaderPanel.tsx @@ -3,7 +3,7 @@ import { PageOptions } from './PageOptions'; export const HeaderPanel = ({ menuHidden, onShowMenuClick }: { menuHidden: boolean; onShowMenuClick: () => void }) => { return ( -
+
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx deleted file mode 100644 index b347ddda0a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/LanguageButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useState } from 'react'; -import { LanguageSelectPopup } from '$app/components/_shared/LanguageSelectPopup'; -import { LanguageOutlined } from '@mui/icons-material'; - -export const LanguageButton = () => { - const [showPopup, setShowPopup] = useState(false); - - return ( - <> - - {showPopup && setShowPopup(false)}>} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx new file mode 100644 index 0000000000..6dd3d60e98 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/MoreMenu.tsx @@ -0,0 +1,45 @@ +import React, { useCallback, useMemo } from 'react'; +import { LogoutSvg } from '$app/components/_shared/svg/LogoutSvg'; +import { useAuth } from '$app/components/auth/auth.hooks'; +import MenuItem from '@mui/material/MenuItem'; +import { useTranslation } from 'react-i18next'; + +function MoreMenu({ onClose }: { onClose: () => void }) { + const { t } = useTranslation(); + const { logout } = useAuth(); + const onSignOutClick = useCallback(async () => { + await logout(); + onClose(); + }, [onClose, logout]); + + const items = useMemo(() => { + return [ + { + title: t('button.signOut'), + icon: ( + + + + ), + onClick: onSignOutClick, + }, + ]; + }, [onSignOutClick, t]); + + return ( + <> + {items.map((item, index) => { + return ( + +
+ {item.icon} + {item.title} +
+
+ ); + })} + + ); +} + +export default MoreMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx deleted file mode 100644 index 15423484dd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/OptionsPopup.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect'; -import { LogoutSvg } from '../../_shared/svg/LogoutSvg'; - -export const OptionsPopup = ({ onSignOutClick, onClose }: { onSignOutClick: () => void; onClose: () => void }) => { - const items: IPopupItem[] = [ - { - title: 'Sign out', - icon: ( - - - - ), - onClick: onSignOutClick, - }, - ]; - return ( - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts index 6f0202c6d3..d724382ec5 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.hooks.ts @@ -1,27 +1,20 @@ -import { useState } from 'react'; +import { useCallback, useState } from 'react'; import { useAuth } from '../../auth/auth.hooks'; export const usePageOptions = () => { - const [showOptionsPopup, setShowOptionsPopup] = useState(false); - const { logout } = useAuth(); + const [anchorEl, setAnchorEl] = useState(); - const onOptionsClick = () => { - setShowOptionsPopup(true); - }; + const onOptionsClick = useCallback((el: HTMLDivElement | HTMLButtonElement) => { + setAnchorEl(el); + }, []); const onClose = () => { - setShowOptionsPopup(false); - }; - - const onSignOutClick = async () => { - await logout(); - onClose(); + setAnchorEl(undefined); }; return { - showOptionsPopup, + anchorEl, onOptionsClick, onClose, - onSignOutClick, }; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx index 9b136e9d73..a06ed0f4ac 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/HeaderPanel/PageOptions.tsx @@ -1,30 +1,73 @@ -import { Button } from '../../_shared/Button'; import { Details2Svg } from '../../_shared/svg/Details2Svg'; import { usePageOptions } from './PageOptions.hooks'; -import { OptionsPopup } from './OptionsPopup'; -import { LanguageButton } from '$app/components/layout/HeaderPanel/LanguageButton'; +import { Button, IconButton, List } from '@mui/material'; +import Popover from '@mui/material/Popover'; +import { useCallback, useState } from 'react'; +import MoreMenu from '$app/components/layout/HeaderPanel/MoreMenu'; +import { useTranslation } from 'react-i18next'; +enum PageOptionsEnum { + Share = 'Share', + More = 'More', +} export const PageOptions = () => { - const { showOptionsPopup, onOptionsClick, onClose, onSignOutClick } = usePageOptions(); + const { t } = useTranslation(); + const { anchorEl, onOptionsClick, onClose } = usePageOptions(); + const open = Boolean(anchorEl); + const [option, setOption] = useState(); + const renderMenu = useCallback(() => { + switch (option) { + case PageOptionsEnum.Share: + return
Share
; + default: + return ; + } + }, [onClose, option]); return ( <>
- - - - +
- {showOptionsPopup && } + + {renderMenu()} + ); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx new file mode 100644 index 0000000000..7942ba7a3e --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/MoreMenu.tsx @@ -0,0 +1,84 @@ +import React, { useMemo, useState } from 'react'; +import { EditSvg } from '$app/components/_shared/svg/EditSvg'; +import { TrashSvg } from '$app/components/_shared/svg/TrashSvg'; +import { CopySvg } from '$app/components/_shared/svg/CopySvg'; +import MenuItem from '@mui/material/MenuItem'; +import { useTranslation } from 'react-i18next'; +import RenameDialog from '$app/components/layout/NavigationPanel/RenameDialog'; +import { IPage } from '$app_reducers/pages/slice'; + +function MoreMenu({ + selectedPage, + onRename, + onDeleteClick, + onDuplicateClick, +}: { + selectedPage: IPage; + onRename: (name: string) => Promise; + onDeleteClick: () => void; + onDuplicateClick: () => void; +}) { + const { t } = useTranslation(); + const [renameDialogOpen, setRenameDialogOpen] = useState(false); + + const items = useMemo( + () => [ + { + icon: ( + + + + ), + onClick: () => { + setRenameDialogOpen(true); + }, + title: t('disclosureAction.rename'), + }, + { + icon: ( + + + + ), + onClick: onDeleteClick, + title: t('disclosureAction.delete'), + }, + { + icon: ( + + + + ), + onClick: onDuplicateClick, + title: t('disclosureAction.duplicate'), + }, + ], + [onDeleteClick, onDuplicateClick, t] + ); + + return ( + <> + {items.map((item, index) => { + return ( + +
+ {item.icon} + {item.title} +
+
+ ); + })} + setRenameDialogOpen(false)} + onOk={async (val: string) => { + await onRename(val); + setRenameDialogOpen(false); + }} + /> + + ); +} + +export default MoreMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts index a1737fef76..cca37804bb 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.hooks.ts @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useAppDispatch, useAppSelector } from '$app/stores/store'; import { IPage, pagesActions } from '$app_reducers/pages/slice'; import { ViewLayoutPB } from '@/services/backend'; @@ -10,23 +10,26 @@ import { INITIAL_FOLDER_HEIGHT, PAGE_ITEM_HEIGHT } from '../../_shared/constants import { ViewBackendService } from '$app/stores/effects/folder/view/view_bd_svc'; import { ViewObserver } from '$app/stores/effects/folder/view/view_observer'; +export enum NavItemOptions { + More = 'More', + NewPage = 'NewPage', +} export const useNavItem = (page: IPage) => { const appDispatch = useAppDispatch(); const workspace = useAppSelector((state) => state.workspace); const currentLocation = useLocation(); const [activePageId, setActivePageId] = useState(''); const pages = useAppSelector((state) => state.pages); - + const [anchorEl, setAnchorEl] = useState(); + const menuOpen = Boolean(anchorEl); + const [menuOption, setMenuOption] = useState(); + const [selectedPage, setSelectedPage] = useState(); + const onClickMenuBtn = useCallback((page: IPage, option: NavItemOptions) => { + setSelectedPage(page); + setMenuOption(option); + }, []); const navigate = useNavigate(); - // Actions - const [showPageOptions, setShowPageOptions] = useState(false); - const [showNewPageOptions, setShowNewPageOptions] = useState(false); - const [showRenamePopup, setShowRenamePopup] = useState(false); - - // UI configurations - const [folderHeight, setFolderHeight] = useState(`${INITIAL_FOLDER_HEIGHT}px`); - // backend const service = new ViewBackendService(page.id); const observer = new ViewObserver(page.id); @@ -68,14 +71,6 @@ export const useNavItem = (page: IPage) => { setActivePageId(pageId); }, [currentLocation]); - useEffect(() => { - if (page.showPagesInside) { - setFolderHeight(`${PAGE_ITEM_HEIGHT + getChildCount(page) * PAGE_ITEM_HEIGHT}px`); - } else { - setFolderHeight(`${PAGE_ITEM_HEIGHT}px`); - } - }, [page, pages]); - // recursively get all unfolded child pages const getChildCount: (startPage: IPage) => number = (startPage: IPage) => { let count = 0; @@ -95,42 +90,21 @@ export const useNavItem = (page: IPage) => { appDispatch(pagesActions.toggleShowPages({ id: page.id })); }; - const onPageOptionsClick = () => { - setShowPageOptions((prevState) => !prevState); - }; - - const startPageRename = () => { - setShowRenamePopup(true); - closePopup(); - }; - - const onNewPageClick = () => { - setShowNewPageOptions(!showNewPageOptions); - }; - const changePageTitle = async (newTitle: string) => { await service.update({ name: newTitle }); appDispatch(pagesActions.renamePage({ id: page.id, newTitle })); - }; - - const closeRenamePopup = () => { - setShowRenamePopup(false); + setAnchorEl(undefined); }; const deletePage = async () => { - closePopup(); await service.delete(); appDispatch(pagesActions.deletePage({ id: page.id })); + setAnchorEl(undefined); }; const duplicatePage = async () => { - closePopup(); await service.duplicate(); - }; - - const closePopup = () => { - setShowPageOptions(false); - setShowNewPageOptions(false); + setAnchorEl(undefined); }; const onPageClick = (eventPage: IPage) => { @@ -151,7 +125,6 @@ export const useNavItem = (page: IPage) => { }; const onAddNewPage = async (pageType: ViewLayoutPB) => { - closePopup(); if (!workspace?.id) return; let newPageName = ''; @@ -199,24 +172,15 @@ export const useNavItem = (page: IPage) => { showPagesInside: false, }) ); - + setAnchorEl(undefined); navigate(`/page/${pageTypeRoute}/${newView.id}`); } }; return { onUnfoldClick, - onNewPageClick, - onPageOptionsClick, - startPageRename, changePageTitle, - closeRenamePopup, - closePopup, - - showNewPageOptions, - showPageOptions, - showRenamePopup, deletePage, duplicatePage, @@ -225,7 +189,12 @@ export const useNavItem = (page: IPage) => { onAddNewPage, - folderHeight, activePageId, + menuOpen, + anchorEl, + setAnchorEl, + menuOption, + selectedPage, + onClickMenuBtn, }; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx index e1578faf38..6cc9c29658 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItem.tsx @@ -1,123 +1,124 @@ import { Details2Svg } from '../../_shared/svg/Details2Svg'; import AddSvg from '../../_shared/svg/AddSvg'; -import { NavItemOptionsPopup } from './NavItemOptionsPopup'; -import { NewPagePopup } from './NewPagePopup'; import { IPage } from '$app_reducers/pages/slice'; -import { Button } from '../../_shared/Button'; -import { RenamePopup } from './RenamePopup'; -import { useEffect, useMemo, useRef, useState } from 'react'; +import { useMemo, useRef } from 'react'; import { DropDownShowSvg } from '../../_shared/svg/DropDownShowSvg'; -import { ANIMATION_DURATION, PAGE_ITEM_HEIGHT } from '../../_shared/constants'; -import { useNavItem } from '$app/components/layout/NavigationPanel/NavItem.hooks'; +import { ANIMATION_DURATION } from '../../_shared/constants'; +import { NavItemOptions, useNavItem } from '$app/components/layout/NavigationPanel/NavItem.hooks'; import { useAppSelector } from '$app/stores/store'; import { ViewLayoutPB } from '@/services/backend'; +import Popover from '@mui/material/Popover'; +import { IconButton, List } from '@mui/material'; +import MoreMenu from '$app/components/layout/NavigationPanel/MoreMenu'; +import NewPageMenu from '$app/components/layout/NavigationPanel/NewPageMenu'; export const NavItem = ({ page }: { page: IPage }) => { const pages = useAppSelector((state) => state.pages); const { onUnfoldClick, - onNewPageClick, - onPageOptionsClick, - startPageRename, - changePageTitle, - closeRenamePopup, - closePopup, - - showNewPageOptions, - showPageOptions, - showRenamePopup, - deletePage, duplicatePage, onAddNewPage, - folderHeight, activePageId, onPageClick, + onClickMenuBtn, + menuOpen, + menuOption, + setAnchorEl, + selectedPage, + anchorEl, } = useNavItem(page); - const [popupY, setPopupY] = useState(0); - const el = useRef(null); - useEffect(() => { - if (el.current) { - const { top } = el.current.getBoundingClientRect(); - - setPopupY(top); - } - }, [showPageOptions, showNewPageOptions, showRenamePopup]); - return ( -
-
-
-
-
- -
onPageClick(page)} className={'mr-1 flex h-full min-w-0 items-center text-left'}> - {page.title} + <> +
+
+
+
+
+ +
onPageClick(page)} + className={'mr-1 flex h-full min-w-0 flex-1 items-center text-left'} + > + {page.title} +
+
+
+ { + setAnchorEl(e.currentTarget); + onClickMenuBtn(page, NavItemOptions.More); + }} + > + + + { + setAnchorEl(e.currentTarget); + onClickMenuBtn(page, NavItemOptions.NewPage); + }} + > + +
-
- - -
+
+
+ {useMemo(() => pages.filter((insidePage) => insidePage.parentPageId === page.id), [pages, page]).map( + (insidePage, insideIndex) => ( + + ) + )}
-
- {useMemo(() => pages.filter((insidePage) => insidePage.parentPageId === page.id), [pages, page]).map( - (insidePage, insideIndex) => ( - - ) - )} -
- {showPageOptions && ( - startPageRename()} - onDeleteClick={() => deletePage()} - onDuplicateClick={() => duplicatePage()} - onClose={() => closePopup()} - top={popupY - 124 + 58} - > - )} - {showNewPageOptions && ( - onAddNewPage(ViewLayoutPB.Document)} - onBoardClick={() => onAddNewPage(ViewLayoutPB.Board)} - onGridClick={() => onAddNewPage(ViewLayoutPB.Grid)} - onClose={() => closePopup()} - top={popupY - 124 + 58} - > - )} - {showRenamePopup && ( - changePageTitle(newTitle)} - onClose={closeRenamePopup} - top={popupY - 124 + 40} - > - )} -
+ setAnchorEl(undefined)} + anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} + transformOrigin={{ vertical: 'top', horizontal: 'left' }} + > + + {menuOption === NavItemOptions.More && selectedPage && ( + deletePage()} + onDuplicateClick={() => duplicatePage()} + /> + )} + {menuOption === NavItemOptions.NewPage && ( + onAddNewPage(ViewLayoutPB.Document)} + onBoardClick={() => onAddNewPage(ViewLayoutPB.Board)} + onGridClick={() => onAddNewPage(ViewLayoutPB.Grid)} + /> + )} + + + ); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx deleted file mode 100644 index f8a0a64191..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavItemOptionsPopup.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect'; -import { EditSvg } from '../../_shared/svg/EditSvg'; -import { TrashSvg } from '../../_shared/svg/TrashSvg'; -import { CopySvg } from '../../_shared/svg/CopySvg'; - -export const NavItemOptionsPopup = ({ - onRenameClick, - onDeleteClick, - onDuplicateClick, - onClose, - top, -}: { - onRenameClick: () => void; - onDeleteClick: () => void; - onDuplicateClick: () => void; - onClose?: () => void; - top: number; -}) => { - const items: IPopupItem[] = [ - { - icon: ( - - - - ), - onClick: onRenameClick, - title: 'Rename', - }, - { - icon: ( - - - - ), - onClick: onDeleteClick, - title: 'Delete', - }, - { - icon: ( - - - - ), - onClick: onDuplicateClick, - title: 'Duplicate', - }, - ]; - - return ( - onClose && onClose()} - items={items} - className={`absolute right-0`} - style={{ top: `${top}px` }} - > - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx index 4a1cda6985..1b066d9f53 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NavigationPanel.tsx @@ -54,24 +54,16 @@ export const NavigationPanel = ({ left: `${menuHidden ? -width : 0}px`, }} > -
- - -
-
- p.parentPageId === workspace.id)} /> -
+ + +
+
+ p.parentPageId === workspace.id)} />
-
-
+
+
{/**/} {/**/} @@ -105,7 +97,7 @@ export const TestBackendButton = () => { return ( @@ -118,7 +110,7 @@ export const DesignSpec = () => { return ( @@ -131,7 +123,7 @@ export const AllIcons = () => { return ( diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx new file mode 100644 index 0000000000..7bc2b09929 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPageMenu.tsx @@ -0,0 +1,67 @@ +import React, { useMemo } from 'react'; +import { DocumentSvg } from '$app/components/_shared/svg/DocumentSvg'; +import { BoardSvg } from '$app/components/_shared/svg/BoardSvg'; +import { GridSvg } from '$app/components/_shared/svg/GridSvg'; +import MenuItem from '@mui/material/MenuItem'; +import { useTranslation } from 'react-i18next'; + +function NewPageMenu({ + onDocumentClick, + onGridClick, + onBoardClick, +}: { + onDocumentClick: () => void; + onGridClick: () => void; + onBoardClick: () => void; +}) { + const { t } = useTranslation(); + const items = useMemo( + () => [ + { + icon: ( + + + + ), + onClick: onDocumentClick, + title: t('document.menuName'), + }, + { + icon: ( + + + + ), + onClick: onBoardClick, + title: t('board.menuName'), + }, + { + icon: ( + + + + ), + onClick: onGridClick, + title: t('grid.menuName'), + }, + ], + [onBoardClick, onDocumentClick, onGridClick, t] + ); + + return ( + <> + {items.map((item, index) => { + return ( + +
+ {item.icon} + {item.title} +
+
+ ); + })} + + ); +} + +export default NewPageMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx deleted file mode 100644 index f37b890c68..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewPagePopup.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { IPopupItem, PopupSelect } from '../../_shared/PopupSelect'; -import { DocumentSvg } from '../../_shared/svg/DocumentSvg'; -import { BoardSvg } from '../../_shared/svg/BoardSvg'; -import { GridSvg } from '../../_shared/svg/GridSvg'; - -export const NewPagePopup = ({ - onDocumentClick, - onGridClick, - onBoardClick, - onClose, - top, -}: { - onDocumentClick: () => void; - onGridClick: () => void; - onBoardClick: () => void; - onClose?: () => void; - top: number; -}) => { - const items: IPopupItem[] = [ - { - icon: ( - - - - ), - onClick: onDocumentClick, - title: 'Document', - }, - { - icon: ( - - - - ), - onClick: onBoardClick, - title: 'Board', - }, - { - icon: ( - - - - ), - onClick: onGridClick, - title: 'Grid', - }, - ]; - - return ( - onClose && onClose()} - items={items} - className={'absolute right-0'} - style={{ top: `${top}px` }} - > - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx index b47d126d95..96d533d4a6 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/NewViewButton.tsx @@ -10,10 +10,10 @@ export const NewViewButton = ({ scrollDown }: { scrollDown: () => void }) => { void onNewRootView(); scrollDown(); }} - className={'flex h-[50px] w-full items-center px-6 hover:bg-fill-active'} + className={'flex h-[50px] w-full items-center px-6 hover:bg-fill-list-active'} >
-
+
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx new file mode 100644 index 0000000000..8a0ed3af6a --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenameDialog.tsx @@ -0,0 +1,52 @@ +import React, { useState } from 'react'; +import DialogTitle from '@mui/material/DialogTitle'; +import DialogContent from '@mui/material/DialogContent'; +import Dialog from '@mui/material/Dialog'; +import { useTranslation } from 'react-i18next'; +import TextField from '@mui/material/TextField'; +import { Button, DialogActions } from '@mui/material'; + +function RenameDialog({ + defaultValue, + open, + onClose, + onOk, +}: { + defaultValue: string; + open: boolean; + onClose: () => void; + onOk: (val: string) => void; +}) { + const { t } = useTranslation(); + const [value, setValue] = useState(defaultValue); + + return ( + e.stopPropagation()} open={open} onClose={onClose}> + {t('menuAppHeader.renameDialog')} + + { + setValue(e.target.value); + }} + margin='dense' + fullWidth + variant='standard' + /> + + + + + + + ); +} + +export default RenameDialog; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx deleted file mode 100644 index 1efe2387e4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/RenamePopup.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useEffect, useRef } from 'react'; -import useOutsideClick from '../../_shared/useOutsideClick'; - -export const RenamePopup = ({ - value, - onChange, - onClose, - className = '', - top, -}: { - value: string; - onChange: (newTitle: string) => void; - onClose: () => void; - className?: string; - top?: number; -}) => { - const ref = useRef(null); - const inputRef = useRef(null); - useOutsideClick(ref, () => onClose && onClose()); - - useEffect(() => { - if (!inputRef || !inputRef.current) return; - - const { current: el } = inputRef; - - el.focus(); - el.selectionStart = 0; - el.selectionEnd = el.value.length; - }, [inputRef]); - - return ( -
- onChange(e.target.value)} - /> -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx index 7cf58f7859..3dab5163f1 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/layout/NavigationPanel/TrashButton.tsx @@ -3,7 +3,7 @@ import { TrashSvg } from '$app/components/_shared/svg/TrashSvg'; export const TrashButton = () => { return ( -
diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx index 30ef2efa22..168623b0ef 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx +++ b/frontend/appflowy_tauri/src/appflowy_app/components/tests/ColorPalette.tsx @@ -5,24 +5,24 @@ export const ColorPalette = () => {

Main

-
-
+
+

Tint

-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

Shades

diff --git a/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts b/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts new file mode 100644 index 0000000000..59f0d77cf2 --- /dev/null +++ b/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts @@ -0,0 +1,15 @@ +import i18next from 'i18next'; +import LanguageDetector from 'i18next-browser-languagedetector'; +import { initReactI18next } from 'react-i18next'; +import resourcesToBackend from 'i18next-resources-to-backend'; + +i18next + .use(resourcesToBackend((language: string) => import(`./translations/${language}.json`))) + .use(LanguageDetector) + .use(initReactI18next) + .init({ + lng: 'en', + defaultNS: 'translation', + debug: true, + fallbackLng: 'en', + }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts b/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts index fe3c209491..d10ea3cdff 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/interfaces/index.ts @@ -6,6 +6,7 @@ export interface Document {} export interface UserSetting { theme?: Theme; themeMode?: ThemeMode; + language?: string; } export enum Theme { diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts index b473550bcb..3804e42010 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts @@ -14,6 +14,7 @@ import { UserEventUpdateUserProfile, } from '@/services/backend/events/flowy-user'; import { + BlockActionPB, CreateWorkspacePayloadPB, SignInPayloadPB, SignUpPayloadPB, @@ -95,11 +96,8 @@ export class UserBackendService { return UserEventSignOut(payload); }; - setAppearanceSettings = (params: { theme: string; mode: ThemeModePB }) => { - const payload = AppearanceSettingsPB.fromObject({ - theme: params.theme, - theme_mode: params.mode, - }); + setAppearanceSettings = (params: ReturnType) => { + const payload = AppearanceSettingsPB.fromObject(params); return UserEventSetAppearanceSetting(payload); }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_setting_controller.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_setting_controller.ts index 6c3c668ab5..09718bca35 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_setting_controller.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_setting_controller.ts @@ -27,7 +27,7 @@ export class UserSettingController { return; }; - setAppearanceSetting = async (params: { theme: string; mode: ThemeModePB }) => { + setAppearanceSetting = async (params: ReturnType) => { const res = await this.backendService.setAppearanceSettings(params); if (res.ok) { diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/i18n/initializeI18n.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/i18n/initializeI18n.ts deleted file mode 100644 index ba3f75e7f3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/i18n/initializeI18n.ts +++ /dev/null @@ -1,61 +0,0 @@ -import i18n from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import { initReactI18next } from 'react-i18next'; -import en from '../../../../../appflowy_flutter/assets/translations/en.json'; -import ar_SA from '../../../../../appflowy_flutter/assets/translations/ar-SA.json'; -import ca_ES from '../../../../../appflowy_flutter/assets/translations/ca-ES.json'; -import de_DE from '../../../../../appflowy_flutter/assets/translations/de-DE.json'; -import es_VE from '../../../../../appflowy_flutter/assets/translations/es-VE.json'; -import eu_ES from '../../../../../appflowy_flutter/assets/translations/eu-ES.json'; -import fr_CA from '../../../../../appflowy_flutter/assets/translations/fr-CA.json'; -import fr_FR from '../../../../../appflowy_flutter/assets/translations/fr-FR.json'; -import hu_HU from '../../../../../appflowy_flutter/assets/translations/hu-HU.json'; -import id_ID from '../../../../../appflowy_flutter/assets/translations/id-ID.json'; -import it_IT from '../../../../../appflowy_flutter/assets/translations/it-IT.json'; -import ja_JP from '../../../../../appflowy_flutter/assets/translations/ja-JP.json'; -import ko_KR from '../../../../../appflowy_flutter/assets/translations/ko-KR.json'; -import pl_PL from '../../../../../appflowy_flutter/assets/translations/pl-PL.json'; -import pt_BR from '../../../../../appflowy_flutter/assets/translations/pt-BR.json'; -import pt_PT from '../../../../../appflowy_flutter/assets/translations/pt-PT.json'; -import ru_Ru from '../../../../../appflowy_flutter/assets/translations/ru-RU.json'; -import sv from '../../../../../appflowy_flutter/assets/translations/sv.json'; -import tr_TR from '../../../../../appflowy_flutter/assets/translations/tr-TR.json'; -import zh_CN from '../../../../../appflowy_flutter/assets/translations/zh-CN.json'; -import zh_TW from '../../../../../appflowy_flutter/assets/translations/zh-TW.json'; - -export default function () { - void i18n - .use(LanguageDetector) - .use(initReactI18next) - .init({ - resources: { - en: { translation: en }, - 'ar-SA': { translation: ar_SA }, - 'ca-ES': { translation: ca_ES }, - 'de-DE': { translation: de_DE }, - 'es-VE': { translation: es_VE }, - 'eu-ES': { translation: eu_ES }, - 'fr-CA': { translation: fr_CA }, - 'fr-FR': { translation: fr_FR }, - 'hu-HU': { translation: hu_HU }, - 'id-ID': { translation: id_ID }, - 'it-IT': { translation: it_IT }, - 'ja-JP': { translation: ja_JP }, - 'ko-KR': { translation: ko_KR }, - 'pl-PL': { translation: pl_PL }, - 'pt-BR': { translation: pt_BR }, - 'pt-PT': { translation: pt_PT }, - 'ru-RU': { translation: ru_Ru }, - sv: { translation: sv }, - 'tr-TR': { translation: tr_TR }, - 'zh-CN': { translation: zh_CN }, - 'zh-TW': { translation: zh_TW }, - }, - fallbackLng: 'en', - debug: true, - - interpolation: { - escapeValue: false, // not needed for react as it escapes by default - }, - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts b/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts index ed77210660..11f02fe2a0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts @@ -1,2 +1 @@ /// - diff --git a/frontend/appflowy_tauri/src/styles/Calendar.css b/frontend/appflowy_tauri/src/styles/Calendar.css index e680d957ff..0ac995ed51 100644 --- a/frontend/appflowy_tauri/src/styles/Calendar.css +++ b/frontend/appflowy_tauri/src/styles/Calendar.css @@ -133,7 +133,7 @@ .react-calendar__tile--active:enabled:hover, .react-calendar__tile--active:enabled:focus { - @apply bg-fill-hover; + @apply bg-fill-list-hover; } .react-calendar--selectRange .react-calendar__tile--hover { diff --git a/frontend/appflowy_tauri/src/styles/color/dark.css b/frontend/appflowy_tauri/src/styles/color/dark.css deleted file mode 100644 index 77c2f886a6..0000000000 --- a/frontend/appflowy_tauri/src/styles/color/dark.css +++ /dev/null @@ -1,21 +0,0 @@ -:root[data-dark-mode=true] { - --B50: #024165; - --B100: #00709F; - --B200: #A6ECFF; - --B300: #52D1F4; - --B400: #00BCF0; - --B500: #05ADE2; - --B600: #009FD1; - --N00: #1A202C; - --N50: #232B38; - --N100: #3A465A; - --N200: #313C51; - --N300: #363D49; - --N400: #525A69; - --N500: #59647A; - --N600: #7B8A9D; - --N700: #99A6B8; - --N800: #E2E9F2; - --N900: #EFF4FB; - --N1000: #FFFFFF; -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/color/default.css b/frontend/appflowy_tauri/src/styles/color/default.css deleted file mode 100644 index 8e478d983c..0000000000 --- a/frontend/appflowy_tauri/src/styles/color/default.css +++ /dev/null @@ -1,54 +0,0 @@ -@import './dark.css'; -@import './light.css'; - -:root { - --color-text-title: var(--N800); - --color-text-caption: var(--N600); - --color-text-placeholder: var(--N500); - --color-text-disabled: var(--N300); - --color-text-link-default: var(--B400); - --color-text-link-hover: var(--B300); - --color-text-link-pressed: var(--B600); - --color-text-link-disabled: var(--B100); - --color-icon-default: var(--N800); - --color-icon-secondary: var(--N500); - --color-icon-disabled: var(--N400); - --color-fill-default: var(--B400); - --color-fill-hover: var(--B100); - --color-fill-selector: var(--B50); - --color-fill-active: var(--N200); - --color-line-divider: var(--N500); - --color-line-border: var(--N100); - --color-bg-body: var(--N00); - --color-bg-base: var(--N50); - --color-bg-mask: rgba(0, 0, 0, 0.6); - --color-bg-tips: var(--B100); - --color-bg-brand: #24144B; - --color-function-error: #FB006D; - --color-function-warning: #FFD667; - --color-function-success: #66CF80; - --color-function-info: #00BCF0; - --color-content-onfill: var(--N00); - --color-content-default: var(--B400); - --color-content-disabled: var(--B100); - --color-content-hover: var(--B300); - --color-content-pressed: var(--B600); - - --color-tint-1: #E8E0FF; - --color-tint-2: #FFE7FD; - --color-tint-3: #FFE7EE; - --color-tint-4: #FFEFE3; - --color-tint-5: #FFF2CD; - --color-tint-6: #F5FFDC; - --color-tint-7: #DDFFD6; - --color-tint-8: #DEFFF1; - --color-tint-9: #E1FBFF; -} - -:root[data-dark-mode=true] { - --color-bg-mask: rgba(0, 0, 0, 0.7); - --color-function-error: #D32772; - --color-function-warning: #E9B320; - --color-function-success: #3BA856; - --color-function-info: #2E9DBB; -} diff --git a/frontend/appflowy_tauri/src/styles/color/light.css b/frontend/appflowy_tauri/src/styles/color/light.css deleted file mode 100644 index 54c05d6339..0000000000 --- a/frontend/appflowy_tauri/src/styles/color/light.css +++ /dev/null @@ -1,21 +0,0 @@ -:root { - --B50: #F2FCFF; - --B100: #E0F8FF; - --B200: #A6ECFF; - --B300: #52D1F4; - --B400: #00BCF0; - --B500: #05ADE2; - --B600: #009FD1; - --N00: #FFFFFF; - --N50: #F9FAFD; - --N100: #EDEEF2; - --N200: #E2E4EB; - --N300: #F2F2F2; - --N400: #D4D4D5; - --N500: #BDBDBD; - --N600: #828282; - --N700: #4F4F4F; - --N800: #333333; - --N900: #1F2329; - --N1000: #000000; -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/mui.css b/frontend/appflowy_tauri/src/styles/mui.css index a00bb3bb1d..20f3676cd6 100644 --- a/frontend/appflowy_tauri/src/styles/mui.css +++ b/frontend/appflowy_tauri/src/styles/mui.css @@ -1,6 +1,6 @@ .MuiDialog-root [class$="-MuiBackdrop-root-MuiDialog-backdrop"] { - background-color: var(--color-bg-mask); + background-color: var(--bg-mask); } [class$="-MuiPaper-root-MuiPopover-paper"], [class$="-MuiPaper-root-MuiDialog-paper"] { @@ -8,25 +8,43 @@ } [class$='-MuiButtonBase-root-MuiMenuItem-root'].MuiButtonBase-root.Mui-selected { - background-color: var(--color-fill-hover); + background-color: var(--fill-list-active); } [class$='-MuiButtonBase-root-MuiMenuItem-root'].MuiButtonBase-root:hover { - background-color: var(--color-fill-hover); + background-color: var(--fill-list-hover); +} + +.MuiPaper-root.MuiMenu-paper.MuiPopover-paper { + background-image: none; +} + +.MuiButton-contained.MuiButton-containedPrimary.MuiButton-containedPrimary { + color: var(--content-on-fill); } .MuiButtonBase-root.MuiIconButton-root.MuiIconButton-sizeMedium { - color: var(--color-icon-default); + color: var(--icon-primary); border-radius: 4px; } -.MuiButtonBase-root.MuiIconButton-root:hover { - background: var(--color-fill-hover); +.MuiButtonBase-root.MuiIconButton-root[class$='-MuiButtonBase-root-MuiIconButton-root']:hover { + background: var(--fill-list-hover); +} + +.MuiButtonBase-root.MuiIconButton-root { + color: var(--icon-primary); + border-radius: 4px; + padding: 2px; +} + +.MuiButtonBase-root.MuiButton-containedPrimary.MuiButton-contained:hover { + background: var(--fill-default); } .MuiTooltip-tooltip { - background: var(--color-fill-hover) !important; - color: var(--color-text-title) !important; + background: var(--bg-tips) !important; + color: var(--text-title) !important; font-size: 0.85rem !important; border-radius: 8px !important; font-weight: 400 !important; diff --git a/frontend/appflowy_tauri/src/styles/template.css b/frontend/appflowy_tauri/src/styles/template.css index 020474631a..e4239ff9ca 100644 --- a/frontend/appflowy_tauri/src/styles/template.css +++ b/frontend/appflowy_tauri/src/styles/template.css @@ -1,4 +1,4 @@ -@import './color/default.css'; +@import './variables/index.css'; @import './mui.css'; /* stop body from scrolling */ @@ -20,7 +20,7 @@ body { } ::selection { - @apply bg-fill-hover; + @apply bg-content-blue-100; } div[role="textbox"] ::selection { @@ -28,15 +28,15 @@ div[role="textbox"] ::selection { } .btn { - @apply rounded-xl border border-line-border px-4 py-3; + @apply rounded-xl border border-line-divider px-4 py-3; } .btn-primary { - @apply bg-fill-default text-text-title hover:bg-fill-hover; + @apply bg-fill-default text-text-title hover:bg-fill-list-hover; } .input { - @apply rounded-xl border border-line-border px-[18px] py-[14px] text-sm; + @apply rounded-xl border border-line-divider px-[18px] py-[14px] text-sm; } @@ -44,6 +44,7 @@ th { @apply text-left font-normal; } -span[data-slate-placeholder="true"] { +span[data-slate-placeholder="true"]:not(.inline-block-content) { @apply text-text-placeholder; + opacity: 1 !important; } \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/variables/dark.variables.css b/frontend/appflowy_tauri/src/styles/variables/dark.variables.css new file mode 100644 index 0000000000..c185fb164a --- /dev/null +++ b/frontend/appflowy_tauri/src/styles/variables/dark.variables.css @@ -0,0 +1,117 @@ +/** +* Do not edit directly +* Generated on Tue, 11 Jul 2023 06:48:47 GMT +* Generated from $pnpm css:variables +*/ + +:root[data-dark-mode=true] { + --base-light-neutral-50: #f9fafd; + --base-light-neutral-100: #edeef2; + --base-light-neutral-200: #e2e4eb; + --base-light-neutral-300: #f2f2f2; + --base-light-neutral-400: #e0e0e0; + --base-light-neutral-500: #bdbdbd; + --base-light-neutral-600: #828282; + --base-light-neutral-700: #4f4f4f; + --base-light-neutral-800: #333333; + --base-light-neutral-900: #1f2329; + --base-light-neutral-1000: #000000; + --base-light-neutral-00: #ffffff; + --base-light-blue-50: #f2fcff; + --base-light-blue-100: #e0f8ff; + --base-light-blue-200: #a6ecff; + --base-light-blue-300: #52d1f4; + --base-light-blue-400: #00bcf0; + --base-light-blue-500: #05ade2; + --base-light-blue-600: #009fd1; + --base-light-color-deep-red: #fb006d; + --base-light-color-deep-yellow: #ffd667; + --base-light-color-deep-green: #66cf80; + --base-light-color-deep-blue: #00bcf0; + --base-light-color-light-purple: #e8e0ff; + --base-light-color-light-pink: #ffe7ee; + --base-light-color-light-orange: #ffefe3; + --base-light-color-light-yellow: #fff2cd; + --base-light-color-light-lime: #f5ffdc; + --base-light-color-light-green: #ddffd6; + --base-light-color-light-aqua: #defff1; + --base-light-color-light-blue: #e1fbff; + --base-light-color-light-red: #ffe7ee; + --base-black-neutral-100: #252F41; + --base-black-neutral-200: #313c51; + --base-black-neutral-300: #3c4557; + --base-black-neutral-400: #525A69; + --base-black-neutral-500: #59647a; + --base-black-neutral-600: #87A0BF; + --base-black-neutral-700: #99a6b8; + --base-black-neutral-800: #e2e9f2; + --base-black-neutral-900: #eff4fb; + --base-black-neutral-1000: #ffffff; + --base-black-neutral-n50: #232b38; + --base-black-neutral-n00: #1a202c; + --base-black-blue-50: #232b38; + --base-black-blue-100: #005174; + --base-black-blue-200: #a6ecff; + --base-black-blue-300: #52d1f4; + --base-black-blue-400: #00bcf0; + --base-black-blue-500: #05ade2; + --base-black-blue-600: #009fd1; + --base-black-color-deep-red: #d32772; + --base-black-color-deep-yellow: #e9b320; + --base-black-color-deep-green: #3ba856; + --base-black-color-deep-blue: #2e9dbb; + --base-black-color-light-purple: #4D4078; + --base-black-color-light-blue: #2C3B58; + --base-black-color-light-green: #3C5133; + --base-black-color-light-yellow: #695E3E; + --base-black-color-light-pink: #5E3C5E; + --base-black-color-light-red: #56363F; + --base-black-color-light-aqua: #1B3849; + --base-black-color-light-lime: #394027; + --base-else-brand: #2c144b; + --text-title: #e2e9f2; + --text-caption: #87A0BF; + --text-placeholder: #3c4557; + --text-link-default: #00bcf0; + --text-link-hover: #52d1f4; + --text-link-pressed: #009fd1; + --text-link-disabled: #005174; + --icon-primary: #e2e9f2; + --icon-secondary: #59647a; + --icon-disabled: #525A69; + --icon-on-toolbar: #1a202c; + --line-border: #59647a; + --line-divider: #252F41; + --line-on-toolbar: #99a6b8; + --fill-default: #00bcf0; + --fill-hover: #005174; + --fill-toolbar: #e2e9f2; + --fill-selector: #232b38; + --fill-list-active: #252F41; + --fill-list-hover: #005174; + --content-blue-400: #00bcf0; + --content-blue-300: #52d1f4; + --content-blue-600: #009fd1; + --content-blue-100: #005174; + --content-on-fill: #1a202c; + --content-on-tag: #99a6b8; + --content-blue-50: #232b38; + --bg-body: #1a202c; + --bg-base: #232b38; + --bg-mask: rgba(0,0,0,0.7); + --bg-tips: #005174; + --bg-brand: #2c144b; + --function-error: #d32772; + --function-warning: #e9b320; + --function-success: #3ba856; + --function-info: #2e9dbb; + --tint-red: #56363F; + --tint-green: #3C5133; + --tint-purple: #4D4078; + --tint-blue: #2C3B58; + --tint-yellow: #695E3E; + --tint-pink: #5E3C5E; + --tint-lime: #394027; + --tint-aqua: #1B3849; + --shadow: 0px 0px 25px 0px rgba(0,0,0,0.3); +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/variables/index.css b/frontend/appflowy_tauri/src/styles/variables/index.css new file mode 100644 index 0000000000..72aec58eb2 --- /dev/null +++ b/frontend/appflowy_tauri/src/styles/variables/index.css @@ -0,0 +1,2 @@ +@import "./light.variables.css"; +@import "./dark.variables.css"; \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/variables/light.variables.css b/frontend/appflowy_tauri/src/styles/variables/light.variables.css new file mode 100644 index 0000000000..286f189637 --- /dev/null +++ b/frontend/appflowy_tauri/src/styles/variables/light.variables.css @@ -0,0 +1,121 @@ +/** +* Do not edit directly +* Generated on Tue, 11 Jul 2023 06:48:47 GMT +* Generated from $pnpm css:variables +*/ + +:root[data-dark-mode=false] { + --base-light-neutral-50: #f9fafd; + --base-light-neutral-100: #edeef2; + --base-light-neutral-200: #e2e4eb; + --base-light-neutral-300: #f2f2f2; + --base-light-neutral-400: #e0e0e0; + --base-light-neutral-500: #bdbdbd; + --base-light-neutral-600: #828282; + --base-light-neutral-700: #4f4f4f; + --base-light-neutral-800: #333333; + --base-light-neutral-900: #1f2329; + --base-light-neutral-1000: #000000; + --base-light-neutral-00: #ffffff; + --base-light-blue-50: #f2fcff; + --base-light-blue-100: #e0f8ff; + --base-light-blue-200: #a6ecff; + --base-light-blue-300: #52d1f4; + --base-light-blue-400: #00bcf0; + --base-light-blue-500: #05ade2; + --base-light-blue-600: #009fd1; + --base-light-color-deep-red: #fb006d; + --base-light-color-deep-yellow: #ffd667; + --base-light-color-deep-green: #66cf80; + --base-light-color-deep-blue: #00bcf0; + --base-light-color-light-purple: #e8e0ff; + --base-light-color-light-pink: #ffe7ee; + --base-light-color-light-orange: #ffefe3; + --base-light-color-light-yellow: #fff2cd; + --base-light-color-light-lime: #f5ffdc; + --base-light-color-light-green: #ddffd6; + --base-light-color-light-aqua: #defff1; + --base-light-color-light-blue: #e1fbff; + --base-light-color-light-red: #ffe7ee; + --base-black-neutral-100: #252F41; + --base-black-neutral-200: #313c51; + --base-black-neutral-300: #3c4557; + --base-black-neutral-400: #525A69; + --base-black-neutral-500: #59647a; + --base-black-neutral-600: #87A0BF; + --base-black-neutral-700: #99a6b8; + --base-black-neutral-800: #e2e9f2; + --base-black-neutral-900: #eff4fb; + --base-black-neutral-1000: #ffffff; + --base-black-neutral-n50: #232b38; + --base-black-neutral-n00: #1a202c; + --base-black-blue-50: #232b38; + --base-black-blue-100: #005174; + --base-black-blue-200: #a6ecff; + --base-black-blue-300: #52d1f4; + --base-black-blue-400: #00bcf0; + --base-black-blue-500: #05ade2; + --base-black-blue-600: #009fd1; + --base-black-color-deep-red: #d32772; + --base-black-color-deep-yellow: #e9b320; + --base-black-color-deep-green: #3ba856; + --base-black-color-deep-blue: #2e9dbb; + --base-black-color-light-purple: #4D4078; + --base-black-color-light-blue: #2C3B58; + --base-black-color-light-green: #3C5133; + --base-black-color-light-yellow: #695E3E; + --base-black-color-light-pink: #5E3C5E; + --base-black-color-light-red: #56363F; + --base-black-color-light-aqua: #1B3849; + --base-black-color-light-lime: #394027; + --base-else-brand: #2c144b; + --text-title: #333333; + --text-caption: #828282; + --text-placeholder: #bdbdbd; + --text-disabled: #e0e0e0; + --text-link-default: #00bcf0; + --text-link-hover: #52d1f4; + --text-link-pressed: #009fd1; + --text-link-disabled: #e0f8ff; + --icon-primary: #333333; + --icon-secondary: #59647a; + --icon-disabled: #e0e0e0; + --icon-on-toolbar: #ffffff; + --line-border: #bdbdbd; + --line-divider: #edeef2; + --line-on-toolbar: #4f4f4f; + --fill-toolbar: #333333; + --fill-default: #00bcf0; + --fill-hover: #52d1f4; + --fill-pressed: #009fd1; + --fill-active: #e0f8ff; + --fill-list-hover: #e0f8ff; + --fill-list-active: #edeef2; + --content-blue-400: #00bcf0; + --content-blue-300: #52d1f4; + --content-blue-600: #009fd1; + --content-blue-100: #e0f8ff; + --content-blue-50: #f2fcff; + --content-on-fill-hover: #00bcf0; + --content-on-fill: #ffffff; + --content-on-tag: #4f4f4f; + --bg-body: #ffffff; + --bg-base: #f9fafd; + --bg-mask: rgba(0,0,0,0.55); + --bg-tips: #e0f8ff; + --bg-brand: #2c144b; + --function-error: #fb006d; + --function-waring: #ffd667; + --function-success: #66cf80; + --function-info: #00bcf0; + --tint-purple: #e8e0ff; + --tint-pink: #ffe7ee; + --tint-red: #ffe7ee; + --tint-lime: #f5ffdc; + --tint-green: #ddffd6; + --tint-aqua: #defff1; + --tint-blue: #e1fbff; + --tint-orange: #ffefe3; + --tint-yellow: #fff2cd; + --shadow: 0px 0px 10px 0px rgba(0,0,0,0.1); +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/config.cjs b/frontend/appflowy_tauri/style-dictionary/config.cjs new file mode 100644 index 0000000000..a822e14b6e --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/config.cjs @@ -0,0 +1,114 @@ +const StyleDictionary = require('style-dictionary'); +const fs = require('fs'); +const path = require('path'); + +// Add comment header to generated files +StyleDictionary.registerFormat({ + name: 'css/variables', + formatter: function(dictionary, config) { + const header = `/**\n` + '* Do not edit directly\n' + `* Generated on ${new Date().toUTCString()}\n` + `* Generated from $pnpm css:variables \n` + `*/\n\n`; + const allProperties = dictionary.allProperties; + const properties = allProperties.map(prop => { + const { name, value } = prop; + return ` --${name}: ${value};` + }).join('\n'); + // generate tailwind config + generateTailwindConfig(allProperties); + return header + `:root${this.selector} {\n${properties}\n}` + } +}); + +// expand shadow tokens into a single string +StyleDictionary.registerTransform({ + name: 'shadow/spreadShadow', + type: 'value', + matcher: function (prop) { + return prop.type === 'boxShadow'; + }, + transformer: function (prop) { + // destructure shadow values from original token value + const { x, y, blur, spread, color } = prop.original.value; + + return `${x}px ${y}px ${blur}px ${spread}px ${color}`; + }, +}); + +const transforms = ['attribute/cti', 'name/cti/kebab', 'shadow/spreadShadow']; + +// Generate Light CSS variables +StyleDictionary.extend({ + source: ['./style-dictionary/tokens/base.json', './style-dictionary/tokens/light.json'], + platforms: { + css: { + transformGroup: 'css', + buildPath: './src/styles/variables/', + files: [ + { + format: 'css/variables', + destination: 'light.variables.css', + selector: '[data-dark-mode=false]', + options: { + outputReferences: true + } + }, + ], + transforms, + }, + }, +}).buildAllPlatforms(); + +// Generate Dark CSS variables +StyleDictionary.extend({ + source: ['./style-dictionary/tokens/base.json', './style-dictionary/tokens/dark.json'], + platforms: { + css: { + transformGroup: 'css', + buildPath: './src/styles/variables/', + files: [ + { + format: 'css/variables', + destination: 'dark.variables.css', + selector: '[data-dark-mode=true]', + }, + ], + transforms, + }, + }, +}).buildAllPlatforms(); + + +function set(obj, path, value) { + const lastKey = path.pop(); + const lastObj = path.reduce((obj, key) => + obj[key] = obj[key] || {}, + obj); + lastObj[lastKey] = value; +} + +function writeFile (file, data) { + const header = `/**\n` + '* Do not edit directly\n' + `* Generated on ${new Date().toUTCString()}\n` + `* Generated from $pnpm css:variables \n` + `*/\n\n`; + const exportString = `module.exports = ${JSON.stringify(data, null, 2)}`; + fs.writeFileSync(path.join(__dirname, file), header + exportString); +} + +function generateTailwindConfig(allProperties) { + const tailwindColors = {}; + const tailwindBoxShadow = {}; + allProperties.forEach(prop => { + const { path, type, name, value } = prop; + if (path[0] === 'Base') { + return; + } + if (type === 'color') { + if (name.includes('fill')) { + console.log(prop); + } + set(tailwindColors, path, `var(--${name})`); + } + if (type === 'boxShadow') { + set(tailwindBoxShadow, ['md'], `var(--${name})`); + } + }); + writeFile('./tailwind/colors.cjs', tailwindColors); + writeFile('./tailwind/box-shadow.cjs', tailwindBoxShadow); +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs b/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs new file mode 100644 index 0000000000..6fd1c8edc9 --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs @@ -0,0 +1,9 @@ +/** +* Do not edit directly +* Generated on Tue, 11 Jul 2023 06:48:47 GMT +* Generated from $pnpm css:variables +*/ + +module.exports = { + "md": "var(--shadow)" +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs b/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs new file mode 100644 index 0000000000..e465107c3f --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs @@ -0,0 +1,70 @@ +/** +* Do not edit directly +* Generated on Tue, 11 Jul 2023 06:48:47 GMT +* Generated from $pnpm css:variables +*/ + +module.exports = { + "text": { + "title": "var(--text-title)", + "caption": "var(--text-caption)", + "placeholder": "var(--text-placeholder)", + "link-default": "var(--text-link-default)", + "link-hover": "var(--text-link-hover)", + "link-pressed": "var(--text-link-pressed)", + "link-disabled": "var(--text-link-disabled)" + }, + "icon": { + "primary": "var(--icon-primary)", + "secondary": "var(--icon-secondary)", + "disabled": "var(--icon-disabled)", + "on-toolbar": "var(--icon-on-toolbar)" + }, + "line": { + "border": "var(--line-border)", + "divider": "var(--line-divider)", + "on-toolbar": "var(--line-on-toolbar)" + }, + "fill": { + "default": "var(--fill-default)", + "hover": "var(--fill-hover)", + "toolbar": "var(--fill-toolbar)", + "selector": "var(--fill-selector)", + "list": { + "active": "var(--fill-list-active)", + "hover": "var(--fill-list-hover)" + } + }, + "content": { + "blue-400": "var(--content-blue-400)", + "blue-300": "var(--content-blue-300)", + "blue-600": "var(--content-blue-600)", + "blue-100": "var(--content-blue-100)", + "on-fill": "var(--content-on-fill)", + "on-tag": "var(--content-on-tag)", + "blue-50": "var(--content-blue-50)" + }, + "bg": { + "body": "var(--bg-body)", + "base": "var(--bg-base)", + "mask": "var(--bg-mask)", + "tips": "var(--bg-tips)", + "brand": "var(--bg-brand)" + }, + "function": { + "error": "var(--function-error)", + "warning": "var(--function-warning)", + "success": "var(--function-success)", + "info": "var(--function-info)" + }, + "tint": { + "red": "var(--tint-red)", + "green": "var(--tint-green)", + "purple": "var(--tint-purple)", + "blue": "var(--tint-blue)", + "yellow": "var(--tint-yellow)", + "pink": "var(--tint-pink)", + "lime": "var(--tint-lime)", + "aqua": "var(--tint-aqua)" + } +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/base.json b/frontend/appflowy_tauri/style-dictionary/tokens/base.json new file mode 100644 index 0000000000..dfa4922315 --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/tokens/base.json @@ -0,0 +1,286 @@ +{ + "Base": { + "Light": { + "neutral": { + "50": { + "value": "#f9fafd", + "type": "color" + }, + "100": { + "value": "#edeef2", + "type": "color" + }, + "200": { + "value": "#e2e4eb", + "type": "color" + }, + "300": { + "value": "#f2f2f2", + "type": "color" + }, + "400": { + "value": "#e0e0e0", + "type": "color" + }, + "500": { + "value": "#bdbdbd", + "type": "color" + }, + "600": { + "value": "#828282", + "type": "color" + }, + "700": { + "value": "#4f4f4f", + "type": "color" + }, + "800": { + "value": "#333333", + "type": "color" + }, + "900": { + "value": "#1f2329", + "type": "color" + }, + "1000": { + "value": "#000000", + "type": "color" + }, + "00": { + "value": "#ffffff", + "type": "color" + } + }, + "blue": { + "50": { + "value": "#f2fcff", + "type": "color" + }, + "100": { + "value": "#e0f8ff", + "type": "color" + }, + "200": { + "value": "#a6ecff", + "type": "color" + }, + "300": { + "value": "#52d1f4", + "type": "color" + }, + "400": { + "value": "#00bcf0", + "type": "color" + }, + "500": { + "value": "#05ade2", + "type": "color" + }, + "600": { + "value": "#009fd1", + "type": "color" + } + }, + "color": { + "deep": { + "red": { + "value": "#fb006d", + "type": "color" + }, + "yellow": { + "value": "#ffd667", + "type": "color" + }, + "green": { + "value": "#66cf80", + "type": "color" + }, + "blue": { + "value": "#00bcf0", + "type": "color" + } + }, + "light": { + "purple": { + "value": "#e8e0ff", + "type": "color" + }, + "pink": { + "value": "#ffe7ee", + "type": "color" + }, + "orange": { + "value": "#ffefe3", + "type": "color" + }, + "yellow": { + "value": "#fff2cd", + "type": "color" + }, + "lime": { + "value": "#f5ffdc", + "type": "color" + }, + "green": { + "value": "#ddffd6", + "type": "color" + }, + "aqua": { + "value": "#defff1", + "type": "color" + }, + "blue": { + "value": "#e1fbff", + "type": "color" + }, + "red": { + "value": "#ffe7ee", + "type": "color" + } + } + } + }, + "black": { + "neutral": { + "100": { + "value": "#252F41", + "type": "color" + }, + "200": { + "value": "#313c51", + "type": "color" + }, + "300": { + "value": "#3c4557", + "type": "color" + }, + "400": { + "value": "#525A69", + "type": "color" + }, + "500": { + "value": "#59647a", + "type": "color" + }, + "600": { + "value": "#87A0BF", + "type": "color" + }, + "700": { + "value": "#99a6b8", + "type": "color" + }, + "800": { + "value": "#e2e9f2", + "type": "color" + }, + "900": { + "value": "#eff4fb", + "type": "color" + }, + "1000": { + "value": "#ffffff", + "type": "color" + }, + "N50": { + "value": "#232b38", + "type": "color" + }, + "N00": { + "value": "#1a202c", + "type": "color" + } + }, + "blue": { + "50": { + "value": "#232b38", + "type": "color" + }, + "100": { + "value": "#005174", + "type": "color" + }, + "200": { + "value": "#a6ecff", + "type": "color" + }, + "300": { + "value": "#52d1f4", + "type": "color" + }, + "400": { + "value": "#00bcf0", + "type": "color" + }, + "500": { + "value": "#05ade2", + "type": "color" + }, + "600": { + "value": "#009fd1", + "type": "color" + } + }, + "color": { + "deep": { + "red": { + "value": "#d32772", + "type": "color" + }, + "yellow": { + "value": "#e9b320", + "type": "color" + }, + "green": { + "value": "#3ba856", + "type": "color" + }, + "blue": { + "value": "#2e9dbb", + "type": "color" + } + }, + "light": { + "purple": { + "value": "#4D4078", + "type": "color" + }, + "blue": { + "value": "#2C3B58", + "type": "color" + }, + "green": { + "value": "#3C5133", + "type": "color" + }, + "yellow": { + "value": "#695E3E", + "type": "color" + }, + "pink": { + "value": "#5E3C5E", + "type": "color" + }, + "red": { + "value": "#56363F", + "type": "color" + }, + "aqua": { + "value": "#1B3849", + "type": "color" + }, + "lime": { + "value": "#394027", + "type": "color" + } + } + } + }, + "else": { + "brand": { + "value": "#2c144b", + "type": "color" + } + } + } +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/dark.json b/frontend/appflowy_tauri/style-dictionary/tokens/dark.json new file mode 100644 index 0000000000..196c24087a --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/tokens/dark.json @@ -0,0 +1,207 @@ +{ + "text": { + "title": { + "value": "{Base.black.neutral.800}", + "type": "color" + }, + "caption": { + "value": "{Base.black.neutral.600}", + "type": "color" + }, + "placeholder": { + "value": "{Base.black.neutral.300}", + "type": "color" + }, + "link-default": { + "value": "{Base.black.blue.400}", + "type": "color" + }, + "link-hover": { + "value": "{Base.black.blue.300}", + "type": "color" + }, + "link-pressed": { + "value": "{Base.black.blue.600}", + "type": "color" + }, + "link-disabled": { + "value": "{Base.black.blue.100}", + "type": "color" + } + }, + "icon": { + "primary": { + "value": "{Base.black.neutral.800}", + "type": "color" + }, + "secondary": { + "value": "{Base.black.neutral.500}", + "type": "color" + }, + "disabled": { + "value": "{Base.black.neutral.400}", + "type": "color" + }, + "on-toolbar": { + "value": "{Base.black.neutral.N00}", + "type": "color" + } + }, + "line": { + "border": { + "value": "{Base.black.neutral.500}", + "type": "color" + }, + "divider": { + "value": "{Base.black.neutral.100}", + "type": "color" + }, + "on-toolbar": { + "value": "{Base.black.neutral.700}", + "type": "color" + } + }, + "fill": { + "default": { + "value": "{Base.black.blue.400}", + "type": "color" + }, + "hover": { + "value": "{Base.black.blue.100}", + "type": "color" + }, + "toolbar": { + "value": "{Base.black.neutral.800}", + "type": "color" + }, + "selector": { + "value": "{Base.black.blue.50}", + "type": "color" + }, + "list": { + "active": { + "value": "{Base.black.neutral.100}", + "type": "color" + }, + "hover": { + "value": "{Base.black.blue.100}", + "type": "color" + } + } + }, + "content": { + "blue-400": { + "value": "{Base.black.blue.400}", + "type": "color" + }, + "blue-300": { + "value": "{Base.black.blue.300}", + "type": "color" + }, + "blue-600": { + "value": "{Base.black.blue.600}", + "type": "color" + }, + "blue-100": { + "value": "{Base.black.blue.100}", + "type": "color" + }, + "on-fill": { + "value": "{Base.black.neutral.N00}", + "type": "color" + }, + "on-tag": { + "value": "{Base.black.neutral.700}", + "type": "color" + }, + "blue-50": { + "value": "{Base.black.blue.50}", + "type": "color" + } + }, + "bg": { + "body": { + "value": "{Base.black.neutral.N00}", + "type": "color" + }, + "base": { + "value": "{Base.black.blue.50}", + "type": "color" + }, + "mask": { + "value": "rgba(0,0,0,0.7)", + "type": "color" + }, + "tips": { + "value": "{Base.black.blue.100}", + "type": "color" + }, + "brand": { + "value": "{Base.else.brand}", + "type": "color" + } + }, + "function": { + "error": { + "value": "{Base.black.color.deep.red}", + "type": "color" + }, + "warning": { + "value": "{Base.black.color.deep.yellow}", + "type": "color" + }, + "success": { + "value": "#3ba856", + "type": "color" + }, + "info": { + "value": "#2e9dbb", + "type": "color" + } + }, + "tint": { + "red": { + "value": "{Base.black.color.light.red}", + "type": "color" + }, + "green": { + "value": "{Base.black.color.light.green}", + "type": "color" + }, + "purple": { + "value": "{Base.black.color.light.purple}", + "type": "color" + }, + "blue": { + "value": "{Base.black.color.light.blue}", + "type": "color" + }, + "yellow": { + "value": "{Base.black.color.light.yellow}", + "type": "color" + }, + "pink": { + "value": "{Base.black.color.light.pink}", + "type": "color" + }, + "lime": { + "value": "{Base.black.color.light.lime}", + "type": "color" + }, + "aqua": { + "value": "{Base.black.color.light.aqua}", + "type": "color" + } + }, + "shadow": { + "value": { + "x": "0", + "y": "0", + "blur": "25", + "spread": "0", + "color": "rgba(0,0,0,0.3)", + "type": "innerShadow" + }, + "type": "boxShadow" + } +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/light.json b/frontend/appflowy_tauri/style-dictionary/tokens/light.json new file mode 100644 index 0000000000..98dcb21505 --- /dev/null +++ b/frontend/appflowy_tauri/style-dictionary/tokens/light.json @@ -0,0 +1,223 @@ +{ + "text": { + "title": { + "value": "{Base.Light.neutral.800}", + "type": "color" + }, + "caption": { + "value": "{Base.Light.neutral.600}", + "type": "color" + }, + "placeholder": { + "value": "{Base.Light.neutral.500}", + "type": "color" + }, + "disabled": { + "value": "{Base.Light.neutral.400}", + "type": "color" + }, + "link-default": { + "value": "{Base.Light.blue.400}", + "type": "color" + }, + "link-hover": { + "value": "{Base.Light.blue.300}", + "type": "color" + }, + "link-pressed": { + "value": "{Base.Light.blue.600}", + "type": "color" + }, + "link-disabled": { + "value": "{Base.Light.blue.100}", + "type": "color" + } + }, + "icon": { + "primary": { + "value": "{Base.Light.neutral.800}", + "type": "color" + }, + "secondary": { + "value": "{Base.black.neutral.500}", + "type": "color" + }, + "disabled": { + "value": "{Base.Light.neutral.400}", + "type": "color" + }, + "on-toolbar": { + "value": "{Base.Light.neutral.00}", + "type": "color" + } + }, + "line": { + "border": { + "value": "{Base.Light.neutral.500}", + "type": "color" + }, + "divider": { + "value": "{Base.Light.neutral.100}", + "type": "color" + }, + "on-toolbar": { + "value": "{Base.Light.neutral.700}", + "type": "color" + } + }, + "fill": { + "toolbar": { + "value": "{Base.Light.neutral.800}", + "type": "color" + }, + "default": { + "value": "{Base.Light.blue.400}", + "type": "color" + }, + "hover": { + "value": "{Base.Light.blue.300}", + "type": "color" + }, + "pressed": { + "value": "{Base.Light.blue.600}", + "type": "color" + }, + "active": { + "value": "{Base.Light.blue.100}", + "type": "color" + }, + "list": { + "hover": { + "value": "{Base.Light.blue.100}", + "type": "color" + }, + "active": { + "value": "{Base.Light.neutral.100}", + "type": "color" + } + } + }, + "content": { + "blue-400": { + "value": "{Base.Light.blue.400}", + "type": "color" + }, + "blue-300": { + "value": "{Base.Light.blue.300}", + "type": "color" + }, + "blue-600": { + "value": "{Base.Light.blue.600}", + "type": "color" + }, + "blue-100": { + "value": "{Base.Light.blue.100}", + "type": "color" + }, + "blue-50": { + "value": "{Base.Light.blue.50}", + "type": "color" + }, + "on-fill-hover": { + "value": "{Base.Light.blue.400}", + "type": "color" + }, + "on-fill": { + "value": "{Base.Light.neutral.00}", + "type": "color" + }, + "on-tag": { + "value": "{Base.Light.neutral.700}", + "type": "color" + } + }, + "bg": { + "body": { + "value": "{Base.Light.neutral.00}", + "type": "color" + }, + "base": { + "value": "{Base.Light.neutral.50}", + "type": "color" + }, + "mask": { + "value": "rgba(0,0,0,0.55)", + "type": "color" + }, + "tips": { + "value": "{Base.Light.blue.100}", + "type": "color" + }, + "brand": { + "value": "{Base.else.brand}", + "type": "color" + } + }, + "function": { + "error": { + "value": "{Base.Light.color.deep.red}", + "type": "color" + }, + "waring": { + "value": "{Base.Light.color.deep.yellow}", + "type": "color" + }, + "success": { + "value": "{Base.Light.color.deep.green}", + "type": "color" + }, + "info": { + "value": "{Base.Light.color.deep.blue}", + "type": "color" + } + }, + "tint": { + "purple": { + "value": "{Base.Light.color.light.purple}", + "type": "color" + }, + "pink": { + "value": "{Base.Light.color.light.pink}", + "type": "color" + }, + "red": { + "value": "{Base.Light.color.light.red}", + "type": "color" + }, + "lime": { + "value": "{Base.Light.color.light.lime}", + "type": "color" + }, + "green": { + "value": "{Base.Light.color.light.green}", + "type": "color" + }, + "aqua": { + "value": "{Base.Light.color.light.aqua}", + "type": "color" + }, + "blue": { + "value": "{Base.Light.color.light.blue}", + "type": "color" + }, + "orange": { + "value": "{Base.Light.color.light.orange}", + "type": "color" + }, + "yellow": { + "value": "{Base.Light.color.light.yellow}", + "type": "color" + } + }, + "shadow": { + "value": { + "x": "0", + "y": "0", + "blur": "10", + "spread": "0", + "color": "rgba(0,0,0,0.1)", + "type": "dropShadow" + }, + "type": "boxShadow" + } +} \ No newline at end of file diff --git a/frontend/appflowy_tauri/tailwind.config.cjs b/frontend/appflowy_tauri/tailwind.config.cjs index 9cf8921d7e..516095b541 100644 --- a/frontend/appflowy_tauri/tailwind.config.cjs +++ b/frontend/appflowy_tauri/tailwind.config.cjs @@ -1,3 +1,6 @@ +const colors = require('./style-dictionary/tailwind/colors.cjs'); +const boxShadow = require('./style-dictionary/tailwind/box-shadow.cjs'); + /** @type {import('tailwindcss').Config} */ module.exports = { content: [ @@ -8,70 +11,8 @@ module.exports = { darkMode: 'class', theme: { extend: { - colors: { - text: { - title: 'var(--color-text-title)', - caption: 'var(--color-text-caption)', - placeholder: 'var(--color-text-placeholder)', - disabled: 'var(--color-text-disabled)', - link: { - default: 'var(--color-text-link-default)', - hover: 'var(--color-text-link-hover)', - pressed: 'var(--color-text-link-pressed)', - disabled: 'var(--color-text-link-disabled)', - } - }, - content: { - default: 'var(--color-content-default)', - hover: 'var(--color-content-hover)', - pressed: 'var(--color-content-pressed)', - disabled: 'var(--color-content-disabled)', - onfill: 'var(--color-content-onfill)', - }, - icon: { - default: 'var(--color-icon-default)', - secondary: 'var(--color-icon-secondary)', - disabled: 'var(--color-icon-disabled)', - }, - fill: { - default: 'var(--color-fill-default)', - hover: 'var(--color-fill-hover)', - selector: 'var(--color-fill-selector)', - active: 'var(--color-fill-active)', - }, - line: { - divider: 'var(--color-line-divider)', - border: 'var(--color-line-border)', - }, - bg: { - body: 'var(--color-bg-body)', - base: 'var(--color-bg-base)', - mask: 'var(--color-bg-mask)', - brand: 'var(--color-bg-brand)', - tips: 'var(--color-bg-tips)', - }, - function: { - success: 'var(--color-function-success)', - warning: 'var(--color-function-warning)', - error: 'var(--color-function-error)', - info: 'var(--color-function-info)', - }, - - tint: { - 1: 'var(--color-tint-1)', - 2: 'var(--color-tint-2)', - 3: 'var(--color-tint-3)', - 4: 'var(--color-tint-4)', - 5: 'var(--color-tint-5)', - 6: 'var(--color-tint-6)', - 7: 'var(--color-tint-7)', - 8: 'var(--color-tint-8)', - 9: 'var(--color-tint-9)', - } - }, - boxShadow: { - md: '0px 0px 20px rgba(0, 0, 0, 0.1);', - }, + colors, + boxShadow }, }, plugins: [], diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index fa5bdca791..0d1cbc3b6e 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -112,7 +112,9 @@ "quote": "Quote Block", "header": "Header", "highlight": "Highlight", - "color": "Color" + "color": "Color", + "addLink": "Add Link", + "link": "Link" }, "tooltip": { "lightMode": "Switch to Light mode", @@ -122,7 +124,8 @@ "openMenu": "Click to open menu", "dragRow": "Long press to reorder the row", "viewDataBase": "View database", - "referencePage": "This {name} is referenced" + "referencePage": "This {name} is referenced", + "addBlockBelow": "Add a block below" }, "sideBar": { "closeSidebar": "Close side bar", @@ -154,7 +157,12 @@ "tryAgain": "Try again", "discard": "Discard", "replace": "Replace", - "insertBelow": "Insert Below" + "insertBelow": "Insert Below", + "upload": "Upload", + "edit": "Edit", + "delete": "Delete", + "duplicate": "Duplicate", + "done": "Done" }, "label": { "welcome": "Welcome!", @@ -476,6 +484,47 @@ "outline": { "addHeadingToCreateOutline": "Add headings to create a table of contents." } + }, + "textBlock": { + "placeholder": "Type '/' for commands" + }, + "title": { + "placeholder": "Untitled" + }, + "imageBlock": { + "placeholder": "Click to add image", + "upload": { + "label": "Upload", + "placeholder": "Click to upload image" + }, + "url": { + "label": "Image URL", + "placeholder": "Enter image URL" + }, + "support": "Image size limit is 5MB. Supported formats: JPEG, PNG, GIF, SVG", + "error": { + "invalidImage": "Invalid image", + "invalidImageSize": "Image size must be less than 5MB", + "invalidImageFormat": "Image format is not supported. Supported formats: JPEG, PNG, GIF, SVG", + "invalidImageUrl": "Invalid image URL" + } + }, + "codeBlock": { + "language": { + "label": "Language", + "placeholder": "Select language" + } + }, + "inlineLink": { + "placeholder": "Paste or type a link", + "url": { + "label": "Link URL", + "placeholder": "Enter link URL" + }, + "title": { + "label": "Link Title", + "placeholder": "Enter link title" + } } }, "board": { @@ -510,5 +559,18 @@ "title": "AppFlowy Error", "howToFixFallback": "We're sorry for the inconvenience! Submit an issue on our GitHub page that describes your error.", "github": "View on GitHub" - } + }, + "search": { + "label": "Search", + "placeholder": { + "actions": "Search actions..." + } + }, + "message": { + "copy": { + "success": "Copied!", + "fail": "Unable to copy" + } + }, + "unSupportBlock": "The current version does not support this Block." }