From 6c2c7dcd488fc5b56dd1f6c769c08d341ef348e5 Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Fri, 30 Dec 2022 21:40:15 +0800 Subject: [PATCH] milestone: publish alpha version (#637) - document folder - full-text search - blob storage - basic edgeless support Co-authored-by: tzhangchi Co-authored-by: QiShaoXuan Co-authored-by: DiamondThree Co-authored-by: MingLiang Wang Co-authored-by: JimmFly Co-authored-by: Yifeng Wang Co-authored-by: Himself65 Co-authored-by: lawvs <18554747+lawvs@users.noreply.github.com> Co-authored-by: Qi <474021214@qq.com> --- .all-contributorsrc | 441 +++ .changeset/README.md | 8 + .changeset/config.json | 11 + .editorconfig | 13 + .eslintignore | 4 +- .eslintrc.js | 14 - .gitattributes | 20 + .github/deployment/Caddyfile | 3 +- .github/workflows/build-community.yml | 120 - .github/workflows/build-livedemo.yml | 122 - .github/workflows/build-test-version.yml | 11 +- .github/workflows/build.yml | 334 ++ .github/workflows/changlog.yml | 46 + .github/workflows/temp_test.yml | 139 + .github/workflows/tests.yml | 20 +- .gitignore | 3 + .husky/pre-commit | 4 + .nvmrc | 1 + .prettierignore | 1 + .vscode/extensions.json | 3 + .vscode/settings.json | 2 +- CHANGELOG.md | 5 + CODE_OF_CONDUCT.md | 45 + CONTRIBUTING.md | 3 + LICENSE | 395 +- README.md | 66 +- benchmarks/README.md | 3 + docs/contributor-add.md | 9 + docs/jobs/affine.pro-rust.md | 44 + docs/jobs/summary.md | 22 +- package.json | 65 +- packages/app/.env.local.template | 9 + packages/app/.eslintrc.js | 18 - packages/app/CHANGELOG.md | 7 + packages/app/next.config.js | 59 +- packages/app/package.json | 40 +- packages/app/public/globals.css | 2 + .../app/scripts/__tests__/printer.spec.ts | 21 + packages/app/scripts/printer.js | 20 + packages/app/src/components/404/index.tsx | 11 + packages/app/src/components/404/styles.ts | 21 + packages/app/src/components/Header/icons.tsx | 119 - packages/app/src/components/Header/index.tsx | 158 - .../src/components/contact-modal/icons.tsx | 2 +- .../src/components/contact-modal/index.tsx | 35 +- .../app/src/components/contact-modal/style.ts | 60 +- .../src/components/delete-workspace/index.tsx | 97 + .../src/components/edgeless-toolbar/index.tsx | 92 +- .../src/components/edgeless-toolbar/style.ts | 4 +- .../components/editor-mode-switch/index.tsx | 15 +- .../components/editor-mode-switch/style.ts | 68 +- .../app/src/components/editor-provider.tsx | 42 - packages/app/src/components/editor.tsx | 79 - .../app/src/components/example-markdown.ts | 62 - packages/app/src/components/faq/index.tsx | 87 - .../app/src/components/file-upload/index.tsx | 47 + .../src/components/global-modal-provider.tsx | 53 - .../src/components/header/editor-header.tsx | 77 + .../header-right-items/editor-option-menu.tsx | 111 + .../header/header-right-items/sync-user.tsx | 25 + .../theme-mode-switch/icons.tsx | 0 .../theme-mode-switch/index.tsx | 2 +- .../theme-mode-switch/style.ts | 1 + .../header-right-items/trash-button-group.tsx | 53 + packages/app/src/components/header/header.tsx | 71 + packages/app/src/components/header/index.tsx | 3 + .../components/header/page-list-header.tsx | 21 + .../components/header/quick-search-button.tsx | 29 + .../components/{Header => header}/styles.ts | 121 +- .../components/{Header => header}/utils.tsx | 0 .../components/{faq => help-island}/icons.tsx | 0 .../app/src/components/help-island/index.tsx | 86 + .../components/{faq => help-island}/style.ts | 40 +- packages/app/src/components/import/index.tsx | 65 + packages/app/src/components/import/styles.ts | 25 + .../src/components/invite-members/index.tsx | 251 ++ packages/app/src/components/loading/index.tsx | 15 +- .../app/src/components/loading/loading.tsx | 20 + .../src/components/loading/page-loading.tsx | 31 + packages/app/src/components/loading/styled.ts | 41 +- .../login-modal/LoginOptionButton.tsx | 118 + .../app/src/components/login-modal/google.svg | 6 + .../app/src/components/login-modal/icons.tsx | 42 + .../app/src/components/login-modal/index.tsx | 68 + .../app/src/components/mobile-modal/index.tsx | 26 +- .../app/src/components/mobile-modal/styles.ts | 36 +- .../src/components/page-list/date-cell.tsx | 27 + .../app/src/components/page-list/empty.tsx | 17 + .../app/src/components/page-list/index.tsx | 145 + .../components/page-list/operation-cell.tsx | 109 + .../app/src/components/page-list/styles.ts | 51 + .../app/src/components/provider-composer.ts | 18 + .../app/src/components/quick-search/config.ts | 24 + .../src/components/quick-search/footer.tsx | 35 + .../app/src/components/quick-search/index.tsx | 118 + .../app/src/components/quick-search/input.tsx | 90 + .../components/quick-search/noResultSVG.tsx | 95 + .../src/components/quick-search/results.tsx | 102 + .../app/src/components/quick-search/style.ts | 153 + .../src/components/shortcuts-modal/config.ts | 5 + .../src/components/shortcuts-modal/index.tsx | 16 +- .../src/components/shortcuts-modal/style.ts | 21 +- .../src/components/simple-counter/index.ts | 5 +- .../src/components/workspace-layout/index.tsx | 35 + .../src/components/workspace-layout/styles.ts | 18 + .../general/delete/delete.tsx | 84 + .../workspace-setting/general/delete/index.ts | 1 + .../workspace-setting/general/delete/style.ts | 75 + .../workspace-setting/general/general.tsx | 166 + .../workspace-setting/general/index.ts | 1 + .../workspace-setting/general/leave/index.ts | 1 + .../workspace-setting/general/leave/leave.tsx | 64 + .../workspace-setting/general/leave/style.ts | 46 + .../workspace-setting/general/style.ts | 28 + .../src/components/workspace-setting/index.ts | 1 + .../src/components/workspace-setting/style.ts | 230 ++ .../workspace-setting/workspace-setting.tsx | 361 ++ .../SelectorPopperContent.tsx | 184 + .../CreateWorkspaceItem.tsx | 39 + .../CreateWorkspaceItem/index.ts | 1 + .../workspace-create/index.ts | 1 + .../workspace-create/style.ts | 63 + .../workspace-create/workspace-create.tsx | 118 + .../WorkspaceItem/ListItem.tsx | 37 + .../WorkspaceItem/LoginItem.tsx | 52 + .../WorkspaceItem/PrivateWorkspaceItem.tsx | 60 + .../WorkspaceItem/FooterSetting.tsx | 32 + .../WorkspaceItem/FooterUsers.tsx | 29 + .../WorkspaceItem/WorkspaceItem.tsx | 96 + .../WorkspaceItem/WorkspaceItem/index.ts | 1 + .../WorkspaceSelector/WorkspaceItem/index.ts | 5 + .../WorkspaceSelector/WorkspaceItem/styles.ts | 41 + .../WorkspaceSelector/WorkspaceSelector.tsx | 53 + .../WorkspaceSelector/index.ts | 1 + .../WorkspaceSelector/styles.ts | 41 + .../components/workspace-slider-bar/icons.tsx | 13 + .../workspace-slider-bar/icons/icons.tsx | 27 + .../components/workspace-slider-bar/index.tsx | 188 + .../components/workspace-slider-bar/style.ts | 174 + packages/app/src/globals.d.ts | 4 + .../app/src/hooks/use-change-page-meta.ts | 22 + .../app/src/hooks/use-current-page-meta.ts | 37 + .../app/src/hooks/use-ensure-workspace.ts | 56 + packages/app/src/hooks/use-history-update.ts | 37 + packages/app/src/hooks/use-init-workspace.ts | 38 + packages/app/src/hooks/use-local-storage.ts | 33 + packages/app/src/hooks/use-page-helper.ts | 127 + packages/app/src/hooks/use-page-meta-list.ts | 25 + packages/app/src/hooks/use-props-updated.ts | 38 + packages/app/src/libs/i18n/index.ts | 66 + packages/app/src/libs/i18n/resources/bn.json | 22 + packages/app/src/libs/i18n/resources/en.json | 28 + packages/app/src/libs/i18n/resources/fr.json | 29 + packages/app/src/libs/i18n/resources/index.ts | 72 + packages/app/src/libs/i18n/resources/sr.json | 27 + .../app/src/libs/i18n/resources/zh-Hans.json | 29 + .../app/src/libs/i18n/resources/zh-Hant.json | 29 + packages/app/src/libs/i18n/scripts/api.ts | 185 + .../app/src/libs/i18n/scripts/download.ts | 132 + packages/app/src/libs/i18n/scripts/request.ts | 55 + packages/app/src/libs/i18n/scripts/sync.ts | 154 + packages/app/src/libs/i18n/scripts/utils.ts | 3 + .../ConfirmInvitationPage/AlreadyJoined.tsx | 10 + .../ConfirmInvitationPage/Confirm.tsx | 11 + .../ConfirmInvitationPage/LinkExpired.tsx | 8 + .../ConfirmInvitationPage/index.tsx | 21 + packages/app/src/pages/404.tsx | 4 + packages/app/src/pages/_app.tsx | 66 +- packages/app/src/pages/affine.tsx | 30 +- .../app/src/pages/affine/[workspace_id].tsx | 20 + packages/app/src/pages/confirm-invitation.tsx | 8 + packages/app/src/pages/index.tsx | 61 +- .../app/src/pages/invite/[invite_code].tsx | 144 + .../app/src/pages/playground/templates.tsx | 119 + .../workspace/[workspaceId]/[pageId].tsx | 116 + .../src/pages/workspace/[workspaceId]/all.tsx | 26 + .../workspace/[workspaceId]/favorite.tsx | 20 + .../pages/workspace/[workspaceId]/index.tsx | 40 + .../pages/workspace/[workspaceId]/trash.tsx | 22 + packages/app/src/pages/workspace/index.tsx | 21 + .../providers/app-state-provider/context.ts | 67 + .../app-state-provider/dynamic-blocksuite.tsx | 99 + .../src/providers/app-state-provider/hooks.ts | 51 + .../app-state-provider/hooks/use-sync-data.ts | 48 + .../src/providers/app-state-provider/index.ts | 2 + .../providers/app-state-provider/interface.ts | 17 + .../providers/app-state-provider/provider.tsx | 181 + .../app/src/providers/confirm-provider.tsx | 64 + .../src/providers/global-modal-provider.tsx | 123 + .../{styles => providers}/themeProvider.tsx | 52 +- packages/app/src/styles/helper.ts | 35 + packages/app/src/styles/hooks.ts | 4 - packages/app/src/styles/index.ts | 2 - packages/app/src/styles/theme.ts | 24 +- packages/app/src/styles/types.ts | 15 +- packages/app/src/templates/AFFiNE-Docs.md | 25 + .../templates/Welcome-to-AFFiNE-Alpha-v2.0.md | 50 + .../templates/Welcome-to-the-AFFiNE-Alpha.md | 59 + packages/app/src/ui/button/Button.tsx | 62 + packages/app/src/ui/button/IconButton.tsx | 86 + packages/app/src/ui/button/Loading.tsx | 60 + packages/app/src/ui/button/TextButton.tsx | 54 + packages/app/src/ui/button/index.ts | 3 + packages/app/src/ui/button/interface.ts | 24 + packages/app/src/ui/button/styles.ts | 226 ++ packages/app/src/ui/button/utils.ts | 104 + packages/app/src/ui/confirm/Confirm.tsx | 70 + packages/app/src/ui/confirm/index.ts | 1 + packages/app/src/ui/confirm/styles.ts | 34 + packages/app/src/ui/divider/index.ts | 8 + packages/app/src/ui/empty/Empty.tsx | 20 + packages/app/src/ui/empty/EmptySVG.tsx | 87 + packages/app/src/ui/empty/emptyPage.svg | 20 + packages/app/src/ui/empty/index.ts | 1 + packages/app/src/ui/input/Input.tsx | 55 + packages/app/src/ui/input/index.ts | 3 + packages/app/src/ui/input/style.ts | 30 + packages/app/src/ui/layout/Content.tsx | 45 + packages/app/src/ui/layout/Wrapper.tsx | 49 + packages/app/src/ui/layout/index.ts | 2 + .../app/src/ui/menu/{menu.tsx => Menu.tsx} | 0 packages/app/src/ui/menu/MenuItem.tsx | 35 + packages/app/src/ui/menu/index.ts | 5 +- packages/app/src/ui/menu/styles.ts | 48 +- packages/app/src/ui/modal/Modal.tsx | 52 + .../app/src/ui/modal/ModalCloseButton.tsx | 29 + packages/app/src/ui/modal/ModalWrapper.tsx | 19 + packages/app/src/ui/modal/index.tsx | 7 +- packages/app/src/ui/modal/modal.tsx | 32 - packages/app/src/ui/modal/style.ts | 30 - packages/app/src/ui/modal/styles.ts | 47 + packages/app/src/ui/popper/Popper.tsx | 7 +- packages/app/src/ui/table/Table.tsx | 30 + packages/app/src/ui/table/TableBody.tsx | 11 + packages/app/src/ui/table/TableCell.tsx | 14 + packages/app/src/ui/table/TableHead.tsx | 11 + packages/app/src/ui/table/TableRow.tsx | 11 + packages/app/src/ui/table/index.ts | 12 + packages/app/src/ui/table/interface.ts | 8 + packages/app/src/ui/table/styles.ts | 74 + packages/app/src/ui/toast/index.ts | 1 + packages/app/src/ui/toast/toast.ts | 111 + packages/app/src/utils/env.ts | 1 + packages/app/src/utils/get-is-mobile.ts | 2 + packages/app/src/utils/index.ts | 5 + packages/app/src/utils/print-build-info.ts | 10 +- packages/app/src/utils/tools.ts | 11 + packages/app/src/utils/useragent.ts | 82 + packages/data-services/.gitignore | 4 + packages/data-services/package.json | 34 + packages/data-services/playwright.config.ts | 25 + packages/data-services/src/auth.ts | 44 + packages/data-services/src/data-center.ts | 19 + packages/data-services/src/index.ts | 6 + packages/data-services/src/request/events.ts | 28 + packages/data-services/src/request/index.ts | 45 + packages/data-services/src/request/token.ts | 138 + packages/data-services/src/sdks/index.ts | 4 + .../data-services/src/sdks/types/common.ts | 2 + .../data-services/src/sdks/types/index.ts | 1 + packages/data-services/src/sdks/user.hook.ts | 23 + packages/data-services/src/sdks/user.ts | 21 + .../data-services/src/sdks/workspace.hook.ts | 72 + packages/data-services/src/sdks/workspace.ts | 178 + packages/data-services/src/websocket/index.ts | 1 + .../src/websocket/y-websocket.js | 508 +++ .../data-services/tests/datacenter.spec.ts | 8 + packages/data-services/tests/utils.ts | 4 + packages/data-services/tsconfig.json | 25 + packages/logger/package.json | 6 +- packages/logger/src/Logger.tsx | 2 +- playwright.config.ts | 103 +- pnpm-lock.yaml | 3411 +++++++++++++++-- scripts/notify.mjs | 22 +- tests/change-page-mode.spec.ts | 30 +- tests/contact-us.spec.ts | 24 +- tests/exception-page.spec.ts | 14 + tests/invite-code-page.spec.ts | 13 + tests/layout.spec.ts | 21 + tests/libs/keyboard.ts | 65 + tests/libs/load-page.ts | 4 +- tests/local-first-delete-page.spec.ts | 49 + tests/local-first-export-page.spec.ts | 69 + tests/local-first-favorite-page.spec.ts | 72 + tests/local-first-favorites-items.spec.ts | 58 + tests/local-first-new-page.spec.ts | 22 + tests/local-first-openpage-newtab.spec.ts | 28 + tests/local-first-restore-page.spec.ts | 50 + tests/local-first-show-delete-modal.spec.ts | 52 + tests/local-first-trash-page.spec.ts | 37 + tests/local-first-workspace.spec.ts | 18 + tests/login.spec.ts | 44 + tests/quick-search.spec.ts | 108 + tests/shortcuts.spec.ts | 6 +- tests/theme.spec.ts | 19 +- tsconfig.json | 25 + 296 files changed, 16139 insertions(+), 2072 deletions(-) create mode 100644 .all-contributorsrc create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .editorconfig delete mode 100644 .eslintrc.js create mode 100644 .gitattributes delete mode 100644 .github/workflows/build-community.yml delete mode 100644 .github/workflows/build-livedemo.yml create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/changlog.yml create mode 100644 .github/workflows/temp_test.yml create mode 100755 .husky/pre-commit create mode 100644 .nvmrc create mode 100644 .prettierignore create mode 100644 .vscode/extensions.json create mode 100644 CHANGELOG.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 benchmarks/README.md create mode 100644 docs/contributor-add.md create mode 100644 docs/jobs/affine.pro-rust.md create mode 100644 packages/app/.env.local.template delete mode 100644 packages/app/.eslintrc.js create mode 100644 packages/app/CHANGELOG.md create mode 100644 packages/app/scripts/__tests__/printer.spec.ts create mode 100644 packages/app/scripts/printer.js create mode 100644 packages/app/src/components/404/index.tsx create mode 100644 packages/app/src/components/404/styles.ts delete mode 100644 packages/app/src/components/Header/icons.tsx delete mode 100644 packages/app/src/components/Header/index.tsx create mode 100644 packages/app/src/components/delete-workspace/index.tsx delete mode 100644 packages/app/src/components/editor-provider.tsx delete mode 100644 packages/app/src/components/editor.tsx delete mode 100644 packages/app/src/components/example-markdown.ts delete mode 100644 packages/app/src/components/faq/index.tsx create mode 100644 packages/app/src/components/file-upload/index.tsx delete mode 100644 packages/app/src/components/global-modal-provider.tsx create mode 100644 packages/app/src/components/header/editor-header.tsx create mode 100644 packages/app/src/components/header/header-right-items/editor-option-menu.tsx create mode 100644 packages/app/src/components/header/header-right-items/sync-user.tsx rename packages/app/src/components/{ => header/header-right-items}/theme-mode-switch/icons.tsx (100%) rename packages/app/src/components/{ => header/header-right-items}/theme-mode-switch/index.tsx (95%) rename packages/app/src/components/{ => header/header-right-items}/theme-mode-switch/style.ts (96%) create mode 100644 packages/app/src/components/header/header-right-items/trash-button-group.tsx create mode 100644 packages/app/src/components/header/header.tsx create mode 100644 packages/app/src/components/header/index.tsx create mode 100644 packages/app/src/components/header/page-list-header.tsx create mode 100644 packages/app/src/components/header/quick-search-button.tsx rename packages/app/src/components/{Header => header}/styles.ts (57%) rename packages/app/src/components/{Header => header}/utils.tsx (100%) rename packages/app/src/components/{faq => help-island}/icons.tsx (100%) create mode 100644 packages/app/src/components/help-island/index.tsx rename packages/app/src/components/{faq => help-island}/style.ts (67%) create mode 100644 packages/app/src/components/import/index.tsx create mode 100644 packages/app/src/components/import/styles.ts create mode 100644 packages/app/src/components/invite-members/index.tsx create mode 100644 packages/app/src/components/loading/loading.tsx create mode 100644 packages/app/src/components/loading/page-loading.tsx create mode 100644 packages/app/src/components/login-modal/LoginOptionButton.tsx create mode 100644 packages/app/src/components/login-modal/google.svg create mode 100644 packages/app/src/components/login-modal/icons.tsx create mode 100644 packages/app/src/components/login-modal/index.tsx create mode 100644 packages/app/src/components/page-list/date-cell.tsx create mode 100644 packages/app/src/components/page-list/empty.tsx create mode 100644 packages/app/src/components/page-list/index.tsx create mode 100644 packages/app/src/components/page-list/operation-cell.tsx create mode 100644 packages/app/src/components/page-list/styles.ts create mode 100644 packages/app/src/components/provider-composer.ts create mode 100644 packages/app/src/components/quick-search/config.ts create mode 100644 packages/app/src/components/quick-search/footer.tsx create mode 100644 packages/app/src/components/quick-search/index.tsx create mode 100644 packages/app/src/components/quick-search/input.tsx create mode 100644 packages/app/src/components/quick-search/noResultSVG.tsx create mode 100644 packages/app/src/components/quick-search/results.tsx create mode 100644 packages/app/src/components/quick-search/style.ts create mode 100644 packages/app/src/components/workspace-layout/index.tsx create mode 100644 packages/app/src/components/workspace-layout/styles.ts create mode 100644 packages/app/src/components/workspace-setting/general/delete/delete.tsx create mode 100644 packages/app/src/components/workspace-setting/general/delete/index.ts create mode 100644 packages/app/src/components/workspace-setting/general/delete/style.ts create mode 100644 packages/app/src/components/workspace-setting/general/general.tsx create mode 100644 packages/app/src/components/workspace-setting/general/index.ts create mode 100644 packages/app/src/components/workspace-setting/general/leave/index.ts create mode 100644 packages/app/src/components/workspace-setting/general/leave/leave.tsx create mode 100644 packages/app/src/components/workspace-setting/general/leave/style.ts create mode 100644 packages/app/src/components/workspace-setting/general/style.ts create mode 100644 packages/app/src/components/workspace-setting/index.ts create mode 100644 packages/app/src/components/workspace-setting/style.ts create mode 100644 packages/app/src/components/workspace-setting/workspace-setting.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/CreateWorkspaceItem.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/index.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/style.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/ListItem.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/PrivateWorkspaceItem.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/index.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/index.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/styles.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/index.ts create mode 100644 packages/app/src/components/workspace-slider-bar/WorkspaceSelector/styles.ts create mode 100644 packages/app/src/components/workspace-slider-bar/icons.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/icons/icons.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/index.tsx create mode 100644 packages/app/src/components/workspace-slider-bar/style.ts create mode 100644 packages/app/src/globals.d.ts create mode 100644 packages/app/src/hooks/use-change-page-meta.ts create mode 100644 packages/app/src/hooks/use-current-page-meta.ts create mode 100644 packages/app/src/hooks/use-ensure-workspace.ts create mode 100644 packages/app/src/hooks/use-history-update.ts create mode 100644 packages/app/src/hooks/use-init-workspace.ts create mode 100644 packages/app/src/hooks/use-local-storage.ts create mode 100644 packages/app/src/hooks/use-page-helper.ts create mode 100644 packages/app/src/hooks/use-page-meta-list.ts create mode 100644 packages/app/src/hooks/use-props-updated.ts create mode 100644 packages/app/src/libs/i18n/index.ts create mode 100644 packages/app/src/libs/i18n/resources/bn.json create mode 100644 packages/app/src/libs/i18n/resources/en.json create mode 100644 packages/app/src/libs/i18n/resources/fr.json create mode 100644 packages/app/src/libs/i18n/resources/index.ts create mode 100644 packages/app/src/libs/i18n/resources/sr.json create mode 100644 packages/app/src/libs/i18n/resources/zh-Hans.json create mode 100644 packages/app/src/libs/i18n/resources/zh-Hant.json create mode 100644 packages/app/src/libs/i18n/scripts/api.ts create mode 100644 packages/app/src/libs/i18n/scripts/download.ts create mode 100644 packages/app/src/libs/i18n/scripts/request.ts create mode 100644 packages/app/src/libs/i18n/scripts/sync.ts create mode 100644 packages/app/src/libs/i18n/scripts/utils.ts create mode 100644 packages/app/src/pages-content/ConfirmInvitationPage/AlreadyJoined.tsx create mode 100644 packages/app/src/pages-content/ConfirmInvitationPage/Confirm.tsx create mode 100644 packages/app/src/pages-content/ConfirmInvitationPage/LinkExpired.tsx create mode 100644 packages/app/src/pages-content/ConfirmInvitationPage/index.tsx create mode 100644 packages/app/src/pages/404.tsx create mode 100644 packages/app/src/pages/affine/[workspace_id].tsx create mode 100644 packages/app/src/pages/confirm-invitation.tsx create mode 100644 packages/app/src/pages/invite/[invite_code].tsx create mode 100644 packages/app/src/pages/playground/templates.tsx create mode 100644 packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx create mode 100644 packages/app/src/pages/workspace/[workspaceId]/all.tsx create mode 100644 packages/app/src/pages/workspace/[workspaceId]/favorite.tsx create mode 100644 packages/app/src/pages/workspace/[workspaceId]/index.tsx create mode 100644 packages/app/src/pages/workspace/[workspaceId]/trash.tsx create mode 100644 packages/app/src/pages/workspace/index.tsx create mode 100644 packages/app/src/providers/app-state-provider/context.ts create mode 100644 packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx create mode 100644 packages/app/src/providers/app-state-provider/hooks.ts create mode 100644 packages/app/src/providers/app-state-provider/hooks/use-sync-data.ts create mode 100644 packages/app/src/providers/app-state-provider/index.ts create mode 100644 packages/app/src/providers/app-state-provider/interface.ts create mode 100644 packages/app/src/providers/app-state-provider/provider.tsx create mode 100644 packages/app/src/providers/confirm-provider.tsx create mode 100644 packages/app/src/providers/global-modal-provider.tsx rename packages/app/src/{styles => providers}/themeProvider.tsx (63%) delete mode 100644 packages/app/src/styles/hooks.ts create mode 100644 packages/app/src/templates/AFFiNE-Docs.md create mode 100644 packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md create mode 100644 packages/app/src/templates/Welcome-to-the-AFFiNE-Alpha.md create mode 100644 packages/app/src/ui/button/Button.tsx create mode 100644 packages/app/src/ui/button/IconButton.tsx create mode 100644 packages/app/src/ui/button/Loading.tsx create mode 100644 packages/app/src/ui/button/TextButton.tsx create mode 100644 packages/app/src/ui/button/index.ts create mode 100644 packages/app/src/ui/button/interface.ts create mode 100644 packages/app/src/ui/button/styles.ts create mode 100644 packages/app/src/ui/button/utils.ts create mode 100644 packages/app/src/ui/confirm/Confirm.tsx create mode 100644 packages/app/src/ui/confirm/index.ts create mode 100644 packages/app/src/ui/confirm/styles.ts create mode 100644 packages/app/src/ui/divider/index.ts create mode 100644 packages/app/src/ui/empty/Empty.tsx create mode 100644 packages/app/src/ui/empty/EmptySVG.tsx create mode 100644 packages/app/src/ui/empty/emptyPage.svg create mode 100644 packages/app/src/ui/empty/index.ts create mode 100644 packages/app/src/ui/input/Input.tsx create mode 100644 packages/app/src/ui/input/index.ts create mode 100644 packages/app/src/ui/input/style.ts create mode 100644 packages/app/src/ui/layout/Content.tsx create mode 100644 packages/app/src/ui/layout/Wrapper.tsx create mode 100644 packages/app/src/ui/layout/index.ts rename packages/app/src/ui/menu/{menu.tsx => Menu.tsx} (100%) create mode 100644 packages/app/src/ui/menu/MenuItem.tsx create mode 100644 packages/app/src/ui/modal/Modal.tsx create mode 100644 packages/app/src/ui/modal/ModalCloseButton.tsx create mode 100644 packages/app/src/ui/modal/ModalWrapper.tsx delete mode 100644 packages/app/src/ui/modal/modal.tsx delete mode 100644 packages/app/src/ui/modal/style.ts create mode 100644 packages/app/src/ui/modal/styles.ts create mode 100644 packages/app/src/ui/table/Table.tsx create mode 100644 packages/app/src/ui/table/TableBody.tsx create mode 100644 packages/app/src/ui/table/TableCell.tsx create mode 100644 packages/app/src/ui/table/TableHead.tsx create mode 100644 packages/app/src/ui/table/TableRow.tsx create mode 100644 packages/app/src/ui/table/index.ts create mode 100644 packages/app/src/ui/table/interface.ts create mode 100644 packages/app/src/ui/table/styles.ts create mode 100644 packages/app/src/ui/toast/index.ts create mode 100644 packages/app/src/ui/toast/toast.ts create mode 100644 packages/app/src/utils/env.ts create mode 100644 packages/app/src/utils/index.ts create mode 100644 packages/app/src/utils/tools.ts create mode 100644 packages/app/src/utils/useragent.ts create mode 100644 packages/data-services/.gitignore create mode 100644 packages/data-services/package.json create mode 100644 packages/data-services/playwright.config.ts create mode 100644 packages/data-services/src/auth.ts create mode 100644 packages/data-services/src/data-center.ts create mode 100644 packages/data-services/src/index.ts create mode 100644 packages/data-services/src/request/events.ts create mode 100644 packages/data-services/src/request/index.ts create mode 100644 packages/data-services/src/request/token.ts create mode 100644 packages/data-services/src/sdks/index.ts create mode 100644 packages/data-services/src/sdks/types/common.ts create mode 100644 packages/data-services/src/sdks/types/index.ts create mode 100644 packages/data-services/src/sdks/user.hook.ts create mode 100644 packages/data-services/src/sdks/user.ts create mode 100644 packages/data-services/src/sdks/workspace.hook.ts create mode 100644 packages/data-services/src/sdks/workspace.ts create mode 100644 packages/data-services/src/websocket/index.ts create mode 100644 packages/data-services/src/websocket/y-websocket.js create mode 100644 packages/data-services/tests/datacenter.spec.ts create mode 100644 packages/data-services/tests/utils.ts create mode 100644 packages/data-services/tsconfig.json create mode 100644 tests/exception-page.spec.ts create mode 100644 tests/invite-code-page.spec.ts create mode 100644 tests/layout.spec.ts create mode 100644 tests/libs/keyboard.ts create mode 100644 tests/local-first-delete-page.spec.ts create mode 100644 tests/local-first-export-page.spec.ts create mode 100644 tests/local-first-favorite-page.spec.ts create mode 100644 tests/local-first-favorites-items.spec.ts create mode 100644 tests/local-first-new-page.spec.ts create mode 100644 tests/local-first-openpage-newtab.spec.ts create mode 100644 tests/local-first-restore-page.spec.ts create mode 100644 tests/local-first-show-delete-modal.spec.ts create mode 100644 tests/local-first-trash-page.spec.ts create mode 100644 tests/local-first-workspace.spec.ts create mode 100644 tests/login.spec.ts create mode 100644 tests/quick-search.spec.ts create mode 100644 tsconfig.json diff --git a/.all-contributorsrc b/.all-contributorsrc new file mode 100644 index 0000000000..b08412fb49 --- /dev/null +++ b/.all-contributorsrc @@ -0,0 +1,441 @@ +{ + "projectName": "AFFiNE", + "projectOwner": "toeverything", + "repoType": "github", + "repoHost": "https://github.com", + "files": [ + "README.md" + ], + "imageSize": 50, + "commit": false, + "commitConvention": "angular", + "contributorsPerLine": 7, + "badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n", + "contributors": [ + { + "login": "doodlewind", + "name": "Yifeng Wang", + "avatar_url": "https://avatars.githubusercontent.com/u/7312949?v=4", + "profile": "https://github.com/doodlewind", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "darkskygit", + "name": "DarkSky", + "avatar_url": "https://avatars.githubusercontent.com/u/25152247?v=4", + "profile": "https://darksky.eu.org/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "tzhangchi", + "name": "Chi Zhang", + "avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4", + "profile": "http://zhangchi.page/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "alt1o", + "name": "wang xinglong", + "avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4", + "profile": "https://github.com/alt1o", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "DiamondThree", + "name": "DiamondThree", + "avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4", + "profile": "https://github.com/DiamondThree", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "lawvs", + "name": "Whitewater", + "avatar_url": "https://avatars.githubusercontent.com/u/18554747?v=4", + "profile": "https://lawvs.github.io/profile/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "zuoxiaodong0815", + "name": "xiaodong zuo", + "avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4", + "profile": "https://github.com/zuoxiaodong0815", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Himself65", + "name": "Himself65", + "avatar_url": "https://avatars.githubusercontent.com/u/14026360?v=4", + "profile": "https://github.com/Himself65", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "SaikaSakura", + "name": "MingLIang Wang", + "avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4", + "profile": "https://github.com/SaikaSakura", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "QiShaoXuan", + "name": "Qi", + "avatar_url": "https://avatars.githubusercontent.com/u/22772830?v=4", + "profile": "https://github.com/QiShaoXuan", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "linonetwo", + "name": "lin onetwo", + "avatar_url": "https://avatars.githubusercontent.com/u/3746270?v=4", + "profile": "https://onetwo.ren/wiki", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "colelawrence", + "name": "Cole Lawrence", + "avatar_url": "https://avatars.githubusercontent.com/u/2925395?v=4", + "profile": "https://colelawrence.com/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "thorseraq", + "name": "x1a0t", + "avatar_url": "https://avatars.githubusercontent.com/u/20554850?v=4", + "profile": "https://github.com/thorseraq", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "HeJiachen-PM", + "name": "HeJiachen-PM", + "avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4", + "profile": "https://github.com/HeJiachen-PM", + "contributions": [ + "research", + "doc" + ] + }, + { + "login": "joebeijing", + "name": "houjoe", + "avatar_url": "https://avatars.githubusercontent.com/u/22443345?v=4", + "profile": "https://www.notion.so/houjoe/Joe-2a85f5be01004cd2b6a5ad26fbb948b1", + "contributions": [ + "research", + "doc" + ] + }, + { + "login": "Yipei-Operation", + "name": "Yipei Wei", + "avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4", + "profile": "https://github.com/Yipei-Operation", + "contributions": [ + "doc" + ] + }, + { + "login": "VelikaHF", + "name": "Velika", + "avatar_url": "https://avatars.githubusercontent.com/u/121547898?v=4", + "profile": "https://github.com/VelikaHF", + "contributions": [ + "design" + ] + }, + { + "login": "Svaney-ssman", + "name": "Svaney", + "avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4", + "profile": "https://github.com/Svaney-ssman", + "contributions": [ + "design" + ] + }, + { + "login": "fanjing22", + "name": "fanjing22", + "avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4", + "profile": "https://github.com/fanjing22", + "contributions": [ + "design" + ] + }, + { + "login": "xell", + "name": "Guozhu Liu", + "avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4", + "profile": "http://xell.me/", + "contributions": [ + "design" + ] + }, + { + "login": "fyZheng07", + "name": "fyZheng07", + "avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4", + "profile": "https://github.com/fyZheng07", + "contributions": [ + "eventOrganizing", + "userTesting" + ] + }, + { + "login": "CJSS", + "name": "CJSS", + "avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4", + "profile": "https://github.com/CJSS", + "contributions": [ + "doc" + ] + }, + { + "login": "JimmFly", + "name": "JimmFly", + "avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4", + "profile": "https://github.com/JimmFly", + "contributions": [ + "code" + ] + }, + { + "login": "mitsuhatu", + "name": "mitsuhatu", + "avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4", + "profile": "https://github.com/mitsuhatu", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "Austaras", + "name": "Austaras", + "avatar_url": "https://avatars.githubusercontent.com/u/15013925?v=4", + "profile": "https://shockwave.me/", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "uptonking", + "name": "Jin Yao", + "avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4", + "profile": "https://github.com/uptonking", + "contributions": [ + "code", + "doc" + ] + }, + { + "login": "CarlosZoft", + "name": "Carlos Rafael ", + "avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4", + "profile": "https://github.com/CarlosZoft", + "contributions": [ + "code" + ] + }, + { + "login": "caleboleary", + "name": "Caleb OLeary", + "avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4", + "profile": "https://github.com/caleboleary", + "contributions": [ + "code" + ] + }, + { + "login": "westongraham", + "name": "Weston Graham", + "avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4", + "profile": "https://github.com/westongraham", + "contributions": [ + "doc" + ] + }, + { + "login": "pointmax", + "name": "pointmax", + "avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4", + "profile": "https://github.com/pointmax", + "contributions": [ + "doc" + ] + }, + { + "login": "liby", + "name": "Bryan Lee", + "avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4", + "profile": "https://liby.github.io/notes", + "contributions": [ + "code" + ] + }, + { + "login": "chenmoonmo", + "name": "Simon Li", + "avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4", + "profile": "https://github.com/chenmoonmo", + "contributions": [ + "code" + ] + }, + { + "login": "githbq", + "name": "Bob Hu", + "avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4", + "profile": "https://github.com/githbq", + "contributions": [ + "code" + ] + }, + { + "login": "lucky-chap", + "name": "Quavo", + "avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4", + "profile": "https://quavo.vercel.app/", + "contributions": [ + "doc" + ] + }, + { + "login": "LuciNyan", + "name": "子瞻 Luci", + "avatar_url": "https://avatars.githubusercontent.com/u/22126563?v=4", + "profile": "https://github.com/LuciNyan", + "contributions": [ + "code" + ] + }, + { + "login": "m1911star", + "name": "Horus", + "avatar_url": "https://avatars.githubusercontent.com/u/4948120?v=4", + "profile": "http://blog.ipili.me/", + "contributions": [ + "code", + "platform" + ] + }, + { + "login": "fanshyiis", + "name": "Super.x", + "avatar_url": "https://avatars.githubusercontent.com/u/15103283?v=4", + "profile": "https://segmentfault.com/u/qzuser_584786517d31a", + "contributions": [ + "code" + ] + }, + { + "login": "wangyu-1999", + "name": "Wang Yu", + "avatar_url": "https://avatars.githubusercontent.com/u/80874770?v=4", + "profile": "https://wangyu-1999.github.io/", + "contributions": [ + "code" + ] + }, + { + "login": "felixonmars", + "name": "Felix Yan", + "avatar_url": "https://avatars.githubusercontent.com/u/1006477?v=4", + "profile": "https://felixc.at/", + "contributions": [ + "code" + ] + }, + { + "login": "lynettelopez", + "name": "Lynette Lopez", + "avatar_url": "https://avatars.githubusercontent.com/u/32908859?v=4", + "profile": "https://github.com/lynettelopez", + "contributions": [ + "code" + ] + }, + { + "login": "Zheaoli", + "name": "Manjusaka", + "avatar_url": "https://avatars.githubusercontent.com/u/7054676?v=4", + "profile": "http://manjusaka.itscoder.com/", + "contributions": [ + "code" + ] + }, + { + "login": "sudongyuer", + "name": "Frozen FIsh", + "avatar_url": "https://avatars.githubusercontent.com/u/76603360?v=4", + "profile": "https://juejin.cn/user/2867982785579102/posts?sort=popular", + "contributions": [ + "code" + ] + }, + { + "login": "MuhammedFaraz", + "name": "Mohammed Faraz", + "avatar_url": "https://avatars.githubusercontent.com/u/92734739?v=4", + "profile": "https://github.com/MuhammedFaraz", + "contributions": [ + "doc" + ] + }, + { + "login": "Pranav4399", + "name": "Pranav Sriram ", + "avatar_url": "https://avatars.githubusercontent.com/u/28348429?v=4", + "profile": "https://pranavsriram.dev/", + "contributions": [ + "code" + ] + }, + { + "login": "Reson-a", + "name": "Reson-a", + "avatar_url": "https://avatars.githubusercontent.com/u/20806266?v=4", + "profile": "https://github.com/Reson-a", + "contributions": [ + "code" + ] + } + ] +} diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000000..e5b6d8d6a6 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000000..1304e4b714 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [], + "linked": [], + "access": "restricted", + "baseBranch": "feat/filesystem_and_search", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..6e87a003da --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.eslintignore b/.eslintignore index 9809316cc9..63e7a67a1e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,6 @@ **/webpack.config.js **/jest.config.js +**/scripts/*.js **/node_modules/** -.github/** \ No newline at end of file +.github/** +**/__tests__/** diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index ce7b73ee9e..0000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,14 +0,0 @@ -// https://eslint.org/docs/latest/user-guide/configuring -// "off" or 0 - turn the rule off -// "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code) -// "error" or 2 - turn the rule on as an error (exit code will be 1) - -/** @type { import('eslint').Linter.Config } */ -module.exports = { - extends: ['plugin:prettier/recommended'], - rules: { - 'prettier/prettier': 'warn', - }, - - reportUnusedDisableDirectives: true, -}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..9c6024a9c7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +* text=auto eol=lf + +# These files are binary and should be left untouched +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.ttf binary +*.otf binary +*.eot binary +*.woff binary +*.woff2 binary +*.pdf binary +*.tar.gz binary +*.zip binary +*.7z binary diff --git a/.github/deployment/Caddyfile b/.github/deployment/Caddyfile index d261c7aa26..32b8e4e401 100644 --- a/.github/deployment/Caddyfile +++ b/.github/deployment/Caddyfile @@ -15,8 +15,9 @@ Cache-Control "public, max-age=86400, must-revalidate" } - handle_path /api/* { + handle /api/* { reverse_proxy {$API_SERVER} { + health_uri /api/healthz @error status 500 502 503 503 handle_response @error { root * /dist diff --git a/.github/workflows/build-community.yml b/.github/workflows/build-community.yml deleted file mode 100644 index ee88e5489c..0000000000 --- a/.github/workflows/build-community.yml +++ /dev/null @@ -1,120 +0,0 @@ -name: Build Pathfinder Community Version - -on: - push: - branches: [master] - pull_request: - branches: [master] - -# Cancels all previous workflow runs for pull requests that have not completed. -# See https://docs.github.com/en/actions/using-jobs/using-concurrency -concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -jobs: - build: - name: Lint and Build - runs-on: self-hosted - - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2 - with: - version: 'latest' - - - name: Use Node.js - uses: actions/setup-node@v2 - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - with: - node-version: 18.x - cache: 'pnpm' - - - name: Restore cache - uses: actions/cache@v3 - with: - path: | - .next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- - - - name: Install dependencies - run: pnpm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - - - name: Lint - run: | - pnpm lint --max-warnings=0 - - # - name: Test - # run: pnpm test - - - name: Build - run: pnpm build - - - name: Export - run: pnpm export - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - path: ./packages/app/out - - push_to_registry: - # See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images - name: Push Docker image to Docker Hub - if: github.ref == 'refs/heads/master' - runs-on: ubuntu-latest - needs: build - - permissions: - contents: read - packages: write - - env: - REGISTRY: ghcr.io - IMAGE_NAME: 'toeverything/affine-pathfinder-community' - IMAGE_TAG: canary-${{ github.sha }} - IMAGE_TAG_LATEST: nightly-latest - - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: artifact - path: packages/app/out/ - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - ${{ env.IMAGE_TAG }} - ${{ env.IMAGE_TAG_LATEST }} - - - name: Build and push Docker image - uses: docker/build-push-action@v3 - with: - context: . - push: true - file: ./.github/deployment/Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/build-livedemo.yml b/.github/workflows/build-livedemo.yml deleted file mode 100644 index bf1dc8bbaf..0000000000 --- a/.github/workflows/build-livedemo.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Build Pathfinder LiveDemo Version - -on: - push: - branches: [master] - pull_request: - branches: [master] - -# Cancels all previous workflow runs for pull requests that have not completed. -# See https://docs.github.com/en/actions/using-jobs/using-concurrency -concurrency: - # The concurrency group contains the workflow name and the branch name for - # pull requests or the commit hash for any other events. - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} - cancel-in-progress: true - -jobs: - build: - name: Lint and Build - runs-on: self-hosted - - steps: - - uses: actions/checkout@v2 - - uses: pnpm/action-setup@v2 - with: - version: 'latest' - - - name: Use Node.js - uses: actions/setup-node@v2 - with: - node-version: 18.x - registry-url: https://npm.pkg.github.com - scope: '@toeverything' - cache: 'pnpm' - - - run: node scripts/module-resolve/ci.js - - - name: Restore cache - uses: actions/cache@v3 - with: - path: | - .next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- - - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - - - name: Lint - run: | - pnpm lint --max-warnings=0 - - # - name: Test - # run: pnpm test - - - name: Build - run: pnpm build - - - name: Export - run: pnpm export - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - path: ./packages/app/out - - push_to_registry: - # See https://docs.github.com/en/actions/publishing-packages/publishing-docker-images - name: Push Docker image to Docker Hub - if: github.ref == 'refs/heads/master' - runs-on: ubuntu-latest - needs: build - - permissions: - contents: read - packages: write - - env: - REGISTRY: ghcr.io - IMAGE_NAME: 'toeverything/affine-pathfinder' - IMAGE_TAG: canary-${{ github.sha }} - IMAGE_TAG_LATEST: nightly-latest - - steps: - - name: Check out the repo - uses: actions/checkout@v2 - - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: artifact - path: packages/app/out/ - - - name: Log in to Docker Hub - uses: docker/login-action@v2 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - ${{ env.IMAGE_TAG }} - ${{ env.IMAGE_TAG_LATEST }} - - - name: Build and push Docker image - uses: docker/build-push-action@v3 - with: - context: . - push: true - file: ./.github/deployment/Dockerfile - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/build-test-version.yml b/.github/workflows/build-test-version.yml index 265986417d..3862fefa32 100644 --- a/.github/workflows/build-test-version.yml +++ b/.github/workflows/build-test-version.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: inputs: tag: - description: "Custom Tag. Set nightly-latest will publish to development." + description: 'Custom Tag. Set nightly-latest will publish to development.' required: true type: string @@ -20,6 +20,7 @@ jobs: build: name: Lint and Build runs-on: self-hosted + environment: development steps: - uses: actions/checkout@v2 @@ -62,6 +63,14 @@ jobs: - name: Build run: pnpm build + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} - name: Export run: pnpm export diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000000..205e9664d3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,334 @@ +name: Build Pathfinder + +on: + push: + branches: [master] + pull_request: + branches: [master] + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + build: + name: Build on Pull Request + if: github.ref != 'refs/heads/master' + runs-on: self-hosted + environment: development + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + registry-url: https://npm.pkg.github.com + scope: '@toeverything' + cache: 'pnpm' + + - run: node scripts/module-resolve/ci.js + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Build + run: pnpm build + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + + - name: Export + run: pnpm export + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + path: ./packages/app/.next + lint: + name: Lint and E2E Test + runs-on: ubuntu-latest + environment: development + needs: build + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + cache: 'pnpm' + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: packages/app/.next/ + + - name: Lint & E2E Test + run: | + pnpm lint --max-warnings=0 + PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium + PLAYWRIGHT_BROWSERS_PATH=0 pnpm test + PLAYWRIGHT_BROWSERS_PATH=0 pnpm test:dc + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + + build-community: + name: Build Community + if: github.ref == 'refs/heads/master' + runs-on: self-hosted + environment: production + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + cache: 'pnpm' + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Build + run: pnpm build + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + + - name: Export + run: pnpm export + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + path: ./packages/app/out + + publish-community: + name: Push Community Image + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + needs: build-community + + permissions: + contents: read + packages: write + + env: + REGISTRY: ghcr.io + IMAGE_NAME: 'toeverything/affine-pathfinder-community' + IMAGE_TAG: canary-${{ github.sha }} + IMAGE_TAG_LATEST: nightly-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: packages/app/out/ + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + ${{ env.IMAGE_TAG }} + ${{ env.IMAGE_TAG_LATEST }} + + - name: Build Docker image + uses: docker/build-push-action@v3 + with: + context: . + push: true + file: ./.github/deployment/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + build-livedemo: + name: Build LiveDemo + if: github.ref == 'refs/heads/master' + runs-on: self-hosted + environment: production + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + registry-url: https://npm.pkg.github.com + scope: '@toeverything' + cache: 'pnpm' + + - run: node scripts/module-resolve/ci.js + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Build + run: pnpm build + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + + - name: Export + run: pnpm export + + publish-livedemo: + name: Push Livedemo Image + if: github.ref == 'refs/heads/master' + runs-on: ubuntu-latest + needs: build-livedemo + + permissions: + contents: read + packages: write + + env: + REGISTRY: ghcr.io + IMAGE_NAME: 'toeverything/affine-pathfinder' + IMAGE_TAG: canary-${{ github.sha }} + IMAGE_TAG_LATEST: nightly-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: packages/app/out/ + + - name: Log in to Docker Hub + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + ${{ env.IMAGE_TAG }} + ${{ env.IMAGE_TAG_LATEST }} + + - name: Build Docker image + uses: docker/build-push-action@v3 + with: + context: . + push: true + file: ./.github/deployment/Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/changlog.yml b/.github/workflows/changlog.yml new file mode 100644 index 0000000000..75adf3230f --- /dev/null +++ b/.github/workflows/changlog.yml @@ -0,0 +1,46 @@ +name: Pathfinder changelog + +on: + push: + branches: [feat/filesystem_and_search, master] + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + build: + name: publish changelog + runs-on: self-hosted + environment: development + permissions: + pull-requests: write + contents: write + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + registry-url: https://npm.pkg.github.com + scope: '@toeverything' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Create changelog + id: changesets + uses: changesets/action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/temp_test.yml b/.github/workflows/temp_test.yml new file mode 100644 index 0000000000..68c99a8fca --- /dev/null +++ b/.github/workflows/temp_test.yml @@ -0,0 +1,139 @@ +name: Pathfinder Check + +on: + push: + branches: [feat/filesystem_and_search, pathfinder1.1] + pull_request: + branches: [feat/filesystem_and_search, pathfinder1.1] + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +jobs: + build: + name: Build on Pull Request + if: github.ref != 'refs/heads/master' + runs-on: self-hosted + environment: development + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + registry-url: https://npm.pkg.github.com + scope: '@toeverything' + cache: 'pnpm' + + - run: node scripts/module-resolve/ci.js + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install --no-frozen-lockfile + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Build + run: pnpm build + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + + - name: Export + run: pnpm export + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + path: ./packages/app/.next + + lint: + name: Lint and E2E Test + runs-on: ubuntu-latest + environment: development + needs: build + + steps: + - uses: actions/checkout@v2 + - uses: pnpm/action-setup@v2 + with: + version: 'latest' + + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: 18.x + cache: 'pnpm' + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + .next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} + + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: artifact + path: packages/app/.next/ + # Temporary shutdown of E2E testing + - name: Lint & E2E Test + run: | + pnpm lint --max-warnings=0 + env: + NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} + # - name: Lint & E2E Test + # run: | + # pnpm lint --max-warnings=0 + # PLAYWRIGHT_BROWSERS_PATH=0 npx playwright install chromium + # PLAYWRIGHT_BROWSERS_PATH=0 pnpm test + # PLAYWRIGHT_BROWSERS_PATH=0 pnpm test:dc + # env: + # NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + # NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + # NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + # NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + # NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + # NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + # NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index fbc8e8e6ad..94d64c8f46 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,4 +1,4 @@ -name: E2E & Unit Tests +name: Unit Tests on: push: branches: [master] @@ -7,7 +7,7 @@ on: jobs: test: timeout-minutes: 60 - runs-on: self-hosted + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -30,11 +30,19 @@ jobs: env: NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - - name: Install Playwright Browsers - run: npx playwright install --with-deps + - name: Install Playwright browsers + run: npx playwright install chromium - - name: Run E2E tests - run: pnpm run test:e2e + # - name: Run E2E tests + # run: pnpm run test:e2e + # env: + # NEXT_PUBLIC_FIREBASE_API_KEY: ${{ secrets.NEXT_PUBLIC_FIREBASE_API_KEY }} + # NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: ${{ secrets.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN }} + # NEXT_PUBLIC_FIREBASE_PROJECT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_PROJECT_ID }} + # NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: ${{ secrets.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET }} + # NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID }} + # NEXT_PUBLIC_FIREBASE_APP_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_APP_ID }} + # NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: ${{ secrets.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID }} - name: Run Unit tests run: pnpm run test:unit diff --git a/.gitignore b/.gitignore index 2d049c3c6c..24bb279128 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,6 @@ module-resolve.js /test-results/ /playwright-report/ /playwright/.cache/ + +# Cache +.eslintcache diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..58b1861ccc --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +pnpm exec lint-staged diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..b6a7d89c68 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..bd5535a603 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +pnpm-lock.yaml diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..f192bb1ba6 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["ms-playwright.playwright", "esbenp.prettier-vscode"] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index df31aeb4b6..11790ad748 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,5 +3,5 @@ "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.formatOnSave": true, "editor.formatOnSaveMode": "file", - "cSpell.words": ["testid"] + "cSpell.words": ["blocksuite", "livedemo", "testid"] } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..bf3daa76d7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +See the [AFFiNE CHANGELOG](https://affine.pro/blog?tag=Release%20Note) + +--- diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..2e9c0db51e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,45 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at + +For answers to common questions about this code of conduct, see diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..3876d1b89b --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,3 @@ +# Contributing to AFFiNE + +For information related to contributing to AFFiNE, please check out the [Contributing to AFFiNE](https://docs.affine.pro/affine/developer-docs/contributions) section of the documentation at the AFFiNE site. diff --git a/LICENSE b/LICENSE index 296f81d87a..2ddc10a515 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,384 @@ -MIT License +# Mozilla Public License Version 2.0 Copyright (c) Toeverything Technology (Hangzhou) Co., Ltd. and its affiliates. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +1. Definitions -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +--- -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +1.1. "Contributor" +means each individual or legal entity that creates, contributes to +the creation of, or owns Covered Software. + +1.2. "Contributor Version" +means the combination of the Contributions of others (if any) used +by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" +means Covered Software of a particular Contributor. + +1.4. "Covered Software" +means Source Code Form to which the initial Contributor has attached +the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case +including portions thereof. + +1.5. "Incompatible With Secondary Licenses" +means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" +means any form of the work other than Source Code Form. + +1.7. "Larger Work" +means a work that combines Covered Software with other material, in +a separate file or files, that is not Covered Software. + +1.8. "License" +means this document. + +1.9. "Licensable" +means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and +all of the rights conveyed by this License. + +1.10. "Modifications" +means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor +means any patent claim(s), including without limitation, method, +process, and apparatus claims, in any patent Licensable by such +Contributor that would be infringed, but for the grant of the +License, by the making, using, selling, offering for sale, having +made, import, or transfer of either its Contributions or its +Contributor Version. + +1.12. "Secondary License" +means either the GNU General Public License, Version 2.0, the GNU +Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those +licenses. + +1.13. "Source Code Form" +means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") +means an individual or a legal entity exercising rights under this +License. For legal entities, "You" includes any entity that +controls, is controlled by, or is under common control with You. For +purposes of this definition, "control" means (a) the power, direct +or indirect, to cause the direction or management of such entity, +whether by contract or otherwise, or (b) ownership of more than +fifty percent (50%) of the outstanding shares or beneficial +ownership of such entity. + +2. License Grants and Conditions + +--- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) +Licensable by such Contributor to use, reproduce, make available, +modify, display, perform, distribute, and otherwise exploit its +Contributions, either on an unmodified basis, with Modifications, or +as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer +for sale, have made, import, and otherwise transfer either its +Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; +or + +(b) for infringements caused by: (i) Your and any other third party's +modifications of Covered Software, or (ii) the combination of its +Contributions with other software (except as part of its Contributor +Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of +its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities + +--- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code +Form, as described in Section 3.1, and You must inform recipients of +the Executable Form how they can obtain a copy of such Source Code +Form by reasonable means in a timely manner, at a charge no more +than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this +License, or sublicense it under different terms, provided that the +license for the Executable Form does not attempt to limit or alter +the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + +--- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination + +--- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +--- + +- * +- 6. Disclaimer of Warranty \* +- ------------------------- \* +- * +- Covered Software is provided under this License on an "as is" \* +- basis, without warranty of any kind, either expressed, implied, or \* +- statutory, including, without limitation, warranties that the \* +- Covered Software is free of defects, merchantable, fit for a \* +- particular purpose or non-infringing. The entire risk as to the \* +- quality and performance of the Covered Software is with You. \* +- Should any Covered Software prove defective in any respect, You \* +- (not any Contributor) assume the cost of any necessary servicing, \* +- repair, or correction. This disclaimer of warranty constitutes an \* +- essential part of this License. No use of any Covered Software is \* +- authorized under this License except under this disclaimer. \* +- * + +--- + +--- + +- * +- 7. Limitation of Liability \* +- -------------------------- \* +- * +- Under no circumstances and under no legal theory, whether tort \* +- (including negligence), contract, or otherwise, shall any \* +- Contributor, or anyone who distributes Covered Software as \* +- permitted above, be liable to You for any direct, indirect, \* +- special, incidental, or consequential damages of any character \* +- including, without limitation, damages for lost profits, loss of \* +- goodwill, work stoppage, computer failure or malfunction, or any \* +- and all other commercial damages or losses, even if such party \* +- shall have been informed of the possibility of such damages. This \* +- limitation of liability shall not apply to liability for death or \* +- personal injury resulting from such party's negligence to the \* +- extent applicable law prohibits such limitation. Some \* +- jurisdictions do not allow the exclusion or limitation of \* +- incidental or consequential damages, so this exclusion and \* +- limitation may not apply to You. \* +- * + +--- + +8. Litigation + +--- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous + +--- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License + +--- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +## Exhibit A - Source Code Form License Notice + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +## Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as +defined by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index 505dd80128..fd1e2daa8c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066 --> -[all-contributors-badge]: https://img.shields.io/badge/all_contributors-38-orange.svg?style=flat-square +[all-contributors-badge]: https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square @@ -29,6 +29,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066 [![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/) [![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/) [![Rust](https://img.shields.io/badge/Rust-1.62-dea584)](https://www.rust-lang.org/) +
@@ -66,9 +67,11 @@ Before we tell you how to get started with AFFiNE, we'd like to shamelessly plug **Seamless transitions** — However you want your data displayed, whichever viewing mode you use, AFFiNE supports easy transitions to allow you to quickly and effortlessly view your data in the way you want. **Markdown support** — When you write in AFFiNE you can use Markdown syntax which helps create an easier editing experience, that can be experienced with just a keyboard. And this allows you to export your data cleanly into Markdown. + + **Choice of multiple languages** — Thanks to community contributions AFFiNE offers support for multiple languages. If you don't find your language or would like to suggest some changes we welcome your contributions. ## Getting started @@ -89,7 +92,7 @@ For **bug reports**, **feature requests** and other **suggestions** you can also For **translation** and **language support** you can visit our docs for the [internationalization guide].(https://docs.affine.pro/affine/internationalization/welcome) -Looking for **others ways to contribute** and wondering where to start? Check out the [AFFiNE Ambassador program](https://docs.affine.pro/affine/affine-ambassadors/welcome), we work closely with passionate members of our community and provide them with a wide-range of support and resources. +Looking for **others ways to contribute** and wondering where to start? Check out the [AFFiNE Ambassador program](https://docs.affine.pro/affine/affine-ambassadors/welcome), we work closely with passionate members of our community and provide them with a wide-range of support and resources. If you have questions, join us across various [**social platforms**](https://docs.affine.pro/affine/community-links/official-communities) where our friendly community can help provide the answers. @@ -99,21 +102,21 @@ We have done a major refactoring recently, if you want to see our previous versi We would also like to give thanks to open-source projects that make AFFiNE possible: -- [BlockSuite](https://github.com/toeverything/BlockSuite) - AFFiNE is built with and powered by BlockSuite. -- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync. -- [React](https://github.com/facebook/react) -- View layer support and web GUI framework. -- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST. -- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure. -- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor. -- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend. -- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board. -- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library. -- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies) +- [BlockSuite](https://github.com/toeverything/BlockSuite) - AFFiNE is built with and powered by BlockSuite. +- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implementation on state management and data sync. +- [React](https://github.com/facebook/react) -- View layer support and web GUI framework. +- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST. +- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure. +- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor. +- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend. +- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board. +- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library. +- Other [dependencies](https://github.com/toeverything/AFFiNE/network/dependencies) We use the following open source projects to help us build a better development experience: -- [nx](https://github.com/nrwl/nx) -- Awesome monorepo manager & build system -- [tolgee](https://github.com/tolgee/tolgee-platform) -- Elegant i18n collaborative editing platform +- [nx](https://github.com/nrwl/nx) -- Awesome monorepo manager & build system +- [tolgee](https://github.com/tolgee/tolgee-platform) -- Elegant i18n collaborative editing platform Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone. @@ -133,45 +136,54 @@ Thanks a lot to the community for providing such powerful and simple libraries,
xiaodong zuo

💻 📖 +
Himself65

💻 📖
MingLIang Wang

💻 📖
Qi

💻 📖 +
lin onetwo

💻 📖 +
Cole Lawrence

💻 📖 +
x1a0t

💻 📖 +
HeJiachen-PM

🔬 📖 + + +
houjoe

🔬 📖 +
Yipei Wei

📖 +
Velika

🎨 +
Svaney

🎨 +
fanjing22

🎨 +
Guozhu Liu

🎨 +
fyZheng07

📋 📓 + + +
CJSS

📖 +
JimmFly

💻
mitsuhatu

💻 📖
Austaras

💻 📖
Jin Yao

💻 📖 -
HeJiachen-PM

📖 -
Yipei Wei

📖 - - -
fanjing22

🎨 -
Svaney

🎨 -
Guozhu Liu

🎨 -
fyZheng07

📋 📓 -
ShortCipher5

📖 -
JimmFly

💻
Carlos Rafael

💻 +
Caleb OLeary

💻 -
Caleb OLeary

💻
Weston Graham

📖
pointmax

📖
Bryan Lee

💻
Simon Li

💻
Bob Hu

💻
Quavo

📖 +
子瞻 Luci

💻 -
子瞻 Luci

💻
Horus

💻 📦
Super.x

💻
Wang Yu

💻
Felix Yan

💻
Lynette Lopez

💻
Manjusaka

💻 +
Frozen FIsh

💻 -
Frozen FIsh

💻
Mohammed Faraz

📖
Pranav Sriram

💻 +
Reson-a

💻 diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000000..c2e7190ca9 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,3 @@ +# Benchmarks + +Example sites for benchmarking `AFFiNE`. diff --git a/docs/contributor-add.md b/docs/contributor-add.md new file mode 100644 index 0000000000..5cc07bd3ce --- /dev/null +++ b/docs/contributor-add.md @@ -0,0 +1,9 @@ +# Contributor Add + +- https://allcontributors.org/docs/en/cli/usage +- https://allcontributors.org/docs/en/emoji-key + +```shell +all-contributors add tzhangchi code,doc +all-contributors generate +``` diff --git a/docs/jobs/affine.pro-rust.md b/docs/jobs/affine.pro-rust.md new file mode 100644 index 0000000000..7ebfcd3d44 --- /dev/null +++ b/docs/jobs/affine.pro-rust.md @@ -0,0 +1,44 @@ +# What we do + +We, `AFFiNE` believe in shaping a world semantically connected through block components in modern applications. We're open for Fullstack Engineer positions across the OctoBase sub-team. OctoBase is an offline, scalable, and self-contained collaborative database. It provides a data collaboration engine for AFFiNE and BlockSuite. It can either run on the server as a service or be embedded in our client to offer a complete offline computing capacity. + +# Full-stack Engineer + +## This position is for: + +- Developing AFFiNE the open source way, including coding and community engagement. +- Researching and supporting the onboarding process of new use cases for AFFiNE.pro subscribers. +- Improving our data computing engine with Rust. +- Assisting our subscribers in utilizing our product in a data-based way with help from the operational - teams. +- Researching on better activation of potential subscribers. +- Engineers who are self-organized individuals and also responsible team members, no matter whether - they are on-site or working remotely. + +## What we are looking for: + +- Ability to use TypeScript proficiently in engineering projects and at least one server-side development language (preferably Rust). +- Strong English communication and writing skills. +- Ability to work skillfully and comfortably within diverse and cross-functional teams. +- Love open source, share our vision, and work within those values. + +## It would be great if you are: + +- Experience in understanding the architecture and being responsible for the development of a function or module in a real project +- Heavy user of knowledge/project management tools +- Experience in working on a real-world database, distributed server application, or serverless application projects +- Experience in using a collaborative algorithm on your own or participating in projects +- Experienced in working with a globally distributed team. +- Enthusiastic about AFFiNE products as a user or contributor. + +### What we offer: + +- $2800 vouchers for latest generation MacBook Pr or working equipment of your choice. +- Public holidays and paid annual leave starting at 12 days. +- Free lunch, unlimited drinks and snacks. +- Free English language lessons (including free IELTS test) open to all employees. +- Become a maintainer of great open source projects and use Copilot powered by GitHub for free if you want. + +### Contact us: + +Interested? Send us your CV to [contact@toeverything.info](mailto:contact@toeverything.info). + +Feel free to include any extra information (GitHub link, previous projects, personal blog etc.). diff --git a/docs/jobs/summary.md b/docs/jobs/summary.md index 387d665fad..151b6e4f0c 100644 --- a/docs/jobs/summary.md +++ b/docs/jobs/summary.md @@ -1,17 +1,21 @@ # Jobs -- [Fullstack Engineer - Global](./affine.pro-remote.md) @[affine.pro](http://affine.pro/) +- [Fullstack Engineer - Remote](./affine.pro-remote.md) @[affine.pro](http://affine.pro/) - Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , Global + Rust ·TS · BlockSuite · OctoBase(JWST) · Global -- [Fullstack Engineer - China](./affine.pro.md) @[affine.pro](http://affine.pro/) +- [Fullstack Engineer - China](./affine.pro.md) @[affine.pro](http://affine.pro/) - Rust ·TS · BlockSuite · OctoBase(JWST) · Remote , China - -- [Fullstack Engineer Intern](./affine.pro-intern.md) @[affine.pro](http://affine.pro/) + Rust ·TS · BlockSuite · OctoBase(JWST) · China - Rust ·TS · BlockSuite · OctoBase(JWST) · Hangzhou , China +- [Fullstack Engineer - Rust](./affine.pro-rust.md) @[affine.pro](http://affine.pro/) -- [Full Stack Platform Engineer](./mysc.app.md) @[mysc.app](https://mysc.app/) + Rust ·TS · BlockSuite · OctoBase(JWST) · Remote - Rust · JWST · Remote · Shanghai, China +- [Fullstack Engineer - Intern](./affine.pro-intern.md) @[affine.pro](http://affine.pro/) + + Rust ·TS · BlockSuite · OctoBase(JWST) · Hangzhou , China + +- [Full Stack Platform Engineer](./mysc.app.md) @[mysc.app](https://mysc.app/) + + Rust · JWST · Remote · Shanghai, China diff --git a/package.json b/package.json index 975629b874..2bb9544346 100644 --- a/package.json +++ b/package.json @@ -1,22 +1,35 @@ { - "name": "pathfinder", - "version": "0.0.0", + "name": "AFFiNE", + "version": "0.3.0", "private": true, + "author": "toeverything", + "license": "MPL-2.0", "scripts": { - "dev": "pnpm --filter=!@pathfinder/app build && pnpm --filter @pathfinder/app dev", - "build": "pnpm -r build", - "export": "pnpm --filter @pathfinder/app export", - "start": "pnpm --filter @pathfinder/app start", - "lint": "pnpm --filter @pathfinder/app lint", - "test:e2e": "playwright test", + "dev": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev", + "dev:ac": "pnpm --filter=!@affine/app build && pnpm --filter @affine/app dev:ac", + "build": " pnpm --filter=!@affine/app build && pnpm --filter!=@affine/data-services -r build", + "export": "pnpm --filter @affine/app export", + "start": "pnpm --filter @affine/app start", + "lint": "pnpm --filter @affine/app lint", + "test": "playwright test", + "test:dc": "pnpm --filter @affine/data-services test", + "test:e2e:codegen": "npx playwright codegen http://localhost:8080", "test:unit": "jest", + "postinstall": "husky install", "notify": "node --experimental-modules scripts/notify.mjs" }, + "lint-staged": { + "*": "prettier --write --ignore-unknown", + "*.{ts,tsx,js,jsx}": "npx eslint --cache --fix" + }, "devDependencies": { "@jest/globals": "^29.3.1", - "@playwright/test": "^1.28.1", - "@types/node": "18.7.18", - "eslint": "8.22.0", + "@playwright/test": "^1.29.1", + "@typescript-eslint/eslint-plugin": "^5.47.0", + "@typescript-eslint/parser": "^5.47.0", + "@types/eslint": "^8.4.10", + "@types/node": "^18.11.17", + "eslint": "^8.30.0", "eslint-config-next": "12.3.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", @@ -24,6 +37,34 @@ "jest": "^29.3.1", "prettier": "^2.7.1", "ts-jest": "^29.0.3", - "typescript": "^4.9.3" + "typescript": "^4.9.3", + "lint-staged": "^13.1.0", + "husky": "^8.0.2", + "@changesets/cli": "^2.26.0" + }, + "eslintConfig": { + "root": true, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": [ + "./tsconfig.json" + ] + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "prettier/prettier": "warn" + }, + "reportUnusedDisableDirectives": true, + "ignorePatterns": [ + "src/**/*.test.ts", + "package/**/dist/*" + ] } } diff --git a/packages/app/.env.local.template b/packages/app/.env.local.template new file mode 100644 index 0000000000..54ce7d9b51 --- /dev/null +++ b/packages/app/.env.local.template @@ -0,0 +1,9 @@ +NEXT_PUBLIC_FIREBASE_API_KEY= +NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN= +NEXT_PUBLIC_FIREBASE_PROJECT_ID= +NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET= +NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID= +NEXT_PUBLIC_FIREBASE_APP_ID= +NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID= +# absolute path to the block suite directory +LOCAL_BLOCK_SUITE= diff --git a/packages/app/.eslintrc.js b/packages/app/.eslintrc.js deleted file mode 100644 index c76264c249..0000000000 --- a/packages/app/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -// https://eslint.org/docs/latest/user-guide/configuring -// "off" or 0 - turn the rule off -// "warn" or 1 - turn the rule on as a warning (doesn’t affect exit code) -// "error" or 2 - turn the rule on as an error (exit code will be 1) - -/** @type { import('eslint').Linter.Config } */ -module.exports = { - extends: [ - 'next/core-web-vitals', - 'plugin:@next/next/recommended', - 'plugin:prettier/recommended', - ], - rules: { - 'prettier/prettier': 'warn', - }, - - reportUnusedDisableDirectives: true, -}; diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md new file mode 100644 index 0000000000..236fe7333f --- /dev/null +++ b/packages/app/CHANGELOG.md @@ -0,0 +1,7 @@ +# @affine/app + +## 1.0.0 + +### Major Changes + +- cc72448: add changeset config diff --git a/packages/app/next.config.js b/packages/app/next.config.js index 92d847d315..e20b495874 100644 --- a/packages/app/next.config.js +++ b/packages/app/next.config.js @@ -1,10 +1,13 @@ -// @ts-check +/* eslint @typescript-eslint/no-var-requires: "off" */ const { getGitVersion, getCommitHash } = require('./scripts/gitInfo'); +const { dependencies } = require('./package.json'); +const path = require('node:path'); +const printer = require('./scripts/printer').printer; /** @type {import('next').NextConfig} */ const nextConfig = { productionBrowserSourceMaps: true, - reactStrictMode: true, + reactStrictMode: false, swcMinify: false, publicRuntimeConfig: { NODE_ENV: process.env.NODE_ENV, @@ -13,8 +16,58 @@ const nextConfig = { CI: process.env.CI || null, VERSION: getGitVersion(), COMMIT_HASH: getCommitHash(), + EDITOR_VERSION: dependencies['@blocksuite/editor'], + }, + webpack: config => { + config.resolve.alias['yjs'] = require.resolve('yjs'); + config.module.rules.push({ + test: /\.md$/i, + loader: 'raw-loader', + }); + + return config; + }, + images: { + unoptimized: true, + }, + // XXX not test yet + rewrites: async () => { + if (process.env.NODE_API_SERVER === 'ac') { + let destinationAC = 'http://100.85.73.88:12001/api/:path*'; + printer.info('API request proxy to [AC Server] ' + destinationAC); + return [ + { + source: '/api/:path*', + destination: destinationAC, + }, + ]; + } else { + let destinationStandard = 'http://100.77.180.48:11001/api/:path*'; + printer.info( + 'API request proxy to [Standard Server] ' + destinationStandard + ); + + return [ + { + source: '/api/:path*', + destination: destinationStandard, + }, + ]; + } }, basePath: process.env.BASE_PATH, }; -module.exports = nextConfig; +const baseDir = process.env.LOCAL_BLOCK_SUITE ?? '/'; +const withDebugLocal = require('next-debug-local')( + { + '@blocksuite/editor': path.resolve(baseDir, 'packages', 'editor'), + '@blocksuite/blocks': path.resolve(baseDir, 'packages', 'blocks'), + '@blocksuite/store': path.resolve(baseDir, 'packages', 'store'), + }, + { + enable: path.isAbsolute(process.env.LOCAL_BLOCK_SUITE ?? ''), + } +); + +module.exports = withDebugLocal(nextConfig); diff --git a/packages/app/package.json b/packages/app/package.json index a5ad88c36c..37b03ff8c5 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,17 +1,20 @@ { - "name": "@pathfinder/app", - "version": "0.1.0", + "name": "@affine/app", + "version": "0.3.1", "scripts": { - "dev": "next dev", + "dev": "next dev -p 8080", + "dev:ac": "NODE_API_SERVER=ac next dev -p 8080", "build": "next build", "export": "next export", "start": "next start", "lint": "next lint" }, "dependencies": { - "@blocksuite/blocks": "0.3.0-alpha.4", - "@blocksuite/editor": "0.3.0-alpha.4", - "@blocksuite/store": "0.3.0-alpha.4", + "@affine/data-services": "workspace:*", + "@blocksuite/blocks": "0.3.0-20221230100352-5dfe65e", + "@blocksuite/editor": "0.3.0-20221230100352-5dfe65e", + "@blocksuite/icons": "^2.0.2", + "@blocksuite/store": "0.3.0-20221230100352-5dfe65e", "@emotion/css": "^11.10.0", "@emotion/react": "^11.10.4", "@emotion/server": "^11.10.0", @@ -21,24 +24,43 @@ "@mui/base": "^5.0.0-alpha.87", "@mui/icons-material": "^5.10.9", "@mui/material": "^5.8.6", - "@toeverything/pathfinder-logger": "workspace:@pathfinder/logger@*", + "@toeverything/pathfinder-logger": "workspace:@affine/logger@*", + "cmdk": "^0.1.20", "css-spring": "^4.1.0", + "dayjs": "^1.11.7", + "i18next": "^21.9.1", "lit": "^2.3.1", - "next": "13.0.1", + "next": "13.1.0", + "next-debug-local": "^0.1.5", "prettier": "^2.7.1", "quill": "^1.3.7", "quill-cursors": "^4.0.0", "react": "18.2.0", - "react-dom": "18.2.0" + "react-dom": "18.2.0", + "react-i18next": "^11.18.4", + "yjs": "^13.5.43" }, "devDependencies": { "@types/node": "18.7.18", "@types/react": "18.0.20", "@types/react-dom": "18.0.6", + "@types/wicg-file-system-access": "^2020.9.5", + "chalk-next": "^6.1.5", "eslint": "8.22.0", "eslint-config-next": "12.3.1", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", + "raw-loader": "^4.0.2", "typescript": "4.8.3" + }, + "eslintConfig": { + "extends": [ + "next/core-web-vitals", + "plugin:@next/next/recommended" + ], + "rules": { + "prettier/prettier": "warn" + }, + "reportUnusedDisableDirectives": true } } diff --git a/packages/app/public/globals.css b/packages/app/public/globals.css index c3f50fd465..aa58d38563 100644 --- a/packages/app/public/globals.css +++ b/packages/app/public/globals.css @@ -145,7 +145,9 @@ select, keygen, legend { color: var(--affine-text-color); + background-color: unset; outline: 0; + border: 0; font-size: 18px; line-height: 1.5; font-family: var(--affine-font-family); diff --git a/packages/app/scripts/__tests__/printer.spec.ts b/packages/app/scripts/__tests__/printer.spec.ts new file mode 100644 index 0000000000..beb14114e6 --- /dev/null +++ b/packages/app/scripts/__tests__/printer.spec.ts @@ -0,0 +1,21 @@ +import { describe, test, expect } from '@jest/globals'; +import { printer } from './../printer'; +const chalk = require('chalk'); +describe('printer', () => { + test('test debug', () => { + expect(printer.debug('test debug')).toBe( + chalk.green`debug` + chalk.white(' - test debug') + ); + }); + + test('test info', () => { + expect(printer.info('test info')).toBe( + chalk.rgb(19, 167, 205)`info` + chalk.white(' - test info') + ); + }); + test('test warn', () => { + expect(printer.warn('test warn')).toBe( + chalk.yellow`warn` + chalk.white(' - test warn') + ); + }); +}); diff --git a/packages/app/scripts/printer.js b/packages/app/scripts/printer.js new file mode 100644 index 0000000000..8853819ed5 --- /dev/null +++ b/packages/app/scripts/printer.js @@ -0,0 +1,20 @@ +const chalk = require('chalk'); +const printer = { + debug: msg => { + const result = chalk.green`debug` + chalk.white(' - ' + msg); + console.log(result); + return result; + }, + info: msg => { + const result = chalk.rgb(19, 167, 205)`info` + chalk.white(' - ' + msg); + console.log(result); + return result; + }, + warn: msg => { + const result = chalk.yellow`warn` + chalk.white(' - ' + msg); + console.log(result); + return result; + }, +}; + +module.exports = { printer }; diff --git a/packages/app/src/components/404/index.tsx b/packages/app/src/components/404/index.tsx new file mode 100644 index 0000000000..e2f828ad9f --- /dev/null +++ b/packages/app/src/components/404/index.tsx @@ -0,0 +1,11 @@ +import { NotFoundTitle, PageContainer } from './styles'; + +export const NotfoundPage = () => { + return ( + + 404 - Page Not Found + + ); +}; + +export default NotfoundPage; diff --git a/packages/app/src/components/404/styles.ts b/packages/app/src/components/404/styles.ts new file mode 100644 index 0000000000..450be72e68 --- /dev/null +++ b/packages/app/src/components/404/styles.ts @@ -0,0 +1,21 @@ +import { styled } from '@/styles'; + +export const PageContainer = styled('div')(({ theme }) => { + return { + width: '100%', + height: 'calc(100vh)', + backgroundColor: theme.colors.pageBackground, + }; +}); + +export const NotFoundTitle = styled('h1')(({ theme }) => { + return { + position: 'relative', + top: 'calc(50% - 100px)', + height: '100px', + fontSize: '60px', + lineHeight: '100px', + color: theme.colors.textColor, + textAlign: 'center', + }; +}); diff --git a/packages/app/src/components/Header/icons.tsx b/packages/app/src/components/Header/icons.tsx deleted file mode 100644 index 57a4134d6e..0000000000 --- a/packages/app/src/components/Header/icons.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import type { DOMAttributes, CSSProperties } from 'react'; -type IconProps = { - style?: CSSProperties; -} & DOMAttributes; - -export const RightArrow = ({ style = {}, ...props }: IconProps) => { - return ( - - - - ); -}; -export const Export2Markdown = ({ style = {}, ...props }: IconProps) => { - return ( - - - - - ); -}; -export const Export2HTML = ({ style = {}, ...props }: IconProps) => { - return ( - - - - - - - - ); -}; -export const LogoIcon = ({ style = {}, ...props }: IconProps) => { - return ( - - - - ); -}; - -export const MoreIcon = ({ style = {}, ...props }: IconProps) => { - return ( - - - - - - ); -}; -export const ExportIcon = ({ style = {}, ...props }: IconProps) => { - return ( - - - - ); -}; diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx deleted file mode 100644 index 74f4ba2f14..0000000000 --- a/packages/app/src/components/Header/index.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { - LogoIcon, - MoreIcon, - ExportIcon, - Export2Markdown, - Export2HTML, - RightArrow, -} from './icons'; -import { - StyledHeader, - StyledTitle, - StyledTitleWrapper, - StyledLogo, - StyledHeaderRightSide, - IconButton, - StyledHeaderContainer, - StyledBrowserWarning, - StyledCloseButton, - StyledMenuItemWrapper, -} from './styles'; -import { useEditor } from '@/components/editor-provider'; -import EditorModeSwitch from '@/components/editor-mode-switch'; -import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons'; -import ThemeModeSwitch from '@/components/theme-mode-switch'; -import { useModal } from '@/components/global-modal-provider'; -import CloseIcon from '@mui/icons-material/Close'; -import { getWarningMessage, shouldShowWarning } from './utils'; -import { Menu, MenuItem } from '@/ui/menu'; - -const PopoverContent = () => { - const { editor, mode, setMode } = useEditor(); - return ( - <> - { - setMode(mode === 'page' ? 'edgeless' : 'page'); - }} - > - - {mode === 'page' ? : } - Convert to {mode === 'page' ? 'Edgeless' : 'Page'} - - - - { - editor && editor.contentParser.onExportHtml(); - }} - > - - - Export to HTML - - - { - editor && editor.contentParser.onExportMarkdown(); - }} - > - - - Export to Markdown - - - - } - > - - - - Export - - - - - - ); -}; - -const BrowserWarning = ({ onClose }: { onClose: () => void }) => { - return ( - - {getWarningMessage()} - - - - - ); -}; - -export const Header = () => { - const [title, setTitle] = useState(''); - const [isHover, setIsHover] = useState(false); - const [showWarning, setShowWarning] = useState(shouldShowWarning()); - - const { contactModalHandler } = useModal(); - const { editor } = useEditor(); - - useEffect(() => { - if (editor) { - setTitle(editor.model.title || ''); - editor.model.propsUpdated.on(() => { - setTitle(editor.model.title); - }); - } - }, [editor]); - return ( - - { - setShowWarning(false); - }} - /> - - { - contactModalHandler(true); - }} - > - - - {title ? ( - { - setIsHover(true); - }} - onMouseLeave={() => { - setIsHover(false); - }} - > - - {title} - - ) : null} - - - - } placement="bottom-end"> - - - - - - - - ); -}; diff --git a/packages/app/src/components/contact-modal/icons.tsx b/packages/app/src/components/contact-modal/icons.tsx index aba676278d..76079a826e 100644 --- a/packages/app/src/components/contact-modal/icons.tsx +++ b/packages/app/src/components/contact-modal/icons.tsx @@ -71,7 +71,7 @@ export const GithubIcon = () => { ); }; -export const DiscordIcon = (props: any) => { +export const DiscordIcon = () => { return ( , - title: 'Check Our Docs', - subTitle: 'docs.AFFiNE.pro', - link: 'https://docs.affine.pro', + title: 'AFFiNE Community', + subTitle: 'community.affine.pro', + link: 'https://community.affine.pro', }, ]; @@ -75,20 +73,24 @@ type TransitionsModalProps = { export const ContactModal = ({ open, onClose }: TransitionsModalProps) => { return ( - - + + Alpha - { onClose(); }} - > - - + /> @@ -107,10 +109,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => { })} - - Get in touch!
- Join our community. -
+ Get in touch! {linkList.map(({ icon, title, link }) => { return ( @@ -134,7 +133,7 @@ export const ContactModal = ({ open, onClose }: TransitionsModalProps) => {

Copyright © 2022 Toeverything

-
+
); }; diff --git a/packages/app/src/components/contact-modal/style.ts b/packages/app/src/components/contact-modal/style.ts index 13a00d438e..8ac277370c 100644 --- a/packages/app/src/components/contact-modal/style.ts +++ b/packages/app/src/components/contact-modal/style.ts @@ -1,28 +1,11 @@ import { absoluteCenter, displayFlex, styled } from '@/styles'; -import bg from './bg.png'; - -export const StyledModalWrapper = styled('div')(({ theme }) => { - return { - width: '860px', - height: '540px', - backgroundColor: theme.colors.popoverBackground, - backgroundImage: `url(${bg.src})`, - borderRadius: '20px', - position: 'absolute', - left: 0, - right: 0, - top: 0, - bottom: 0, - margin: 'auto', - }; -}); export const StyledBigLink = styled('a')(({ theme }) => { return { - width: '320px', + width: '334px', height: '100px', marginBottom: '48px', - paddingLeft: '114px', + paddingLeft: '90px', fontSize: '24px', lineHeight: '36px', fontWeight: '600', @@ -46,7 +29,7 @@ export const StyledBigLink = styled('a')(({ theme }) => { height: '50px', marginRight: '40px', color: theme.colors.primaryColor, - ...absoluteCenter({ vertical: true, position: { left: '32px' } }), + ...absoluteCenter({ vertical: true, position: { left: '20px' } }), }, p: { width: '100%', @@ -102,6 +85,7 @@ export const StyledSmallLink = styled('a')(({ theme }) => { }); export const StyledSubTitle = styled('div')(({ theme }) => { return { + width: '190px', fontSize: '18px', fontWeight: '600', color: theme.colors.textColor, @@ -136,7 +120,7 @@ export const StyledLogo = styled('img')({ width: 'auto', }); -export const StyledModalHeader = styled('div')(({ theme }) => { +export const StyledModalHeader = styled('div')(() => { return { height: '20px', marginTop: '36px', @@ -162,40 +146,6 @@ export const StyledModalHeaderLeft = styled('div')(({ theme }) => { }; }); -export const StyledCloseButton = styled('div')(({ theme }) => { - return { - width: '60px', - height: '60px', - color: theme.colors.iconColor, - cursor: 'pointer', - ...displayFlex('center', 'center'), - position: 'absolute', - right: '0', - top: '0', - - // TODO: we need to add @emotion/babel-plugin - '::after': { - content: '""', - width: '30px', - height: '30px', - borderRadius: '6px', - ...absoluteCenter({ horizontal: true, vertical: true }), - }, - ':hover': { - color: theme.colors.primaryColor, - '::after': { - background: theme.colors.hoverBackground, - }, - }, - svg: { - width: '20px', - height: '20px', - position: 'relative', - zIndex: 1, - }, - }; -}); - export const StyledModalFooter = styled('div')(({ theme }) => { return { fontSize: '14px', diff --git a/packages/app/src/components/delete-workspace/index.tsx b/packages/app/src/components/delete-workspace/index.tsx new file mode 100644 index 0000000000..5cceb487a2 --- /dev/null +++ b/packages/app/src/components/delete-workspace/index.tsx @@ -0,0 +1,97 @@ +import { styled } from '@/styles'; +import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; +import { Button } from '@/ui/button'; +import Input from '@/ui/input'; +import { useState } from 'react'; + +interface LoginModalProps { + open: boolean; + onClose: () => void; + workSpaceName: string; +} + +export const DeleteModal = ({ + open, + onClose, + workSpaceName, +}: LoginModalProps) => { + const [canDelete, setCanDelete] = useState(true); + const InputChange = (value: string) => { + if (value === workSpaceName) { + setCanDelete(false); + } else { + setCanDelete(true); + } + }; + return ( +
+ + +
+ { + onClose(); + }} + /> +
+ + Delete Workspace +
+ This action cannot be undone. This will permanently delete{' '} + {workSpaceName} workspace name along with all its content. +
+ + +
+
+ + +
+
+
+
+ ); +}; + +const Header = styled('div')({ + position: 'relative', + height: '44px', +}); + +const Content = styled('div')({ + display: 'flex', + padding: '0 48px', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', +}); + +const ContentTitle = styled('h1')({ + fontSize: '20px', + lineHeight: '28px', + fontWeight: 600, + textAlign: 'center', + paddingBottom: '16px', +}); + +const Footer = styled('div')({ + height: '70px', + paddingLeft: '24px', + marginTop: '32px', + textAlign: 'center', +}); diff --git a/packages/app/src/components/edgeless-toolbar/index.tsx b/packages/app/src/components/edgeless-toolbar/index.tsx index bb6478a7c8..c44f6396ca 100644 --- a/packages/app/src/components/edgeless-toolbar/index.tsx +++ b/packages/app/src/components/edgeless-toolbar/index.tsx @@ -16,7 +16,9 @@ import { } from './icons'; import { Tooltip } from '@/ui/tooltip'; import Slide from '@mui/material/Slide'; -import { useEditor } from '@/components/editor-provider'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; +import { useAppState } from '@/providers/app-state-provider'; +import useHistoryUpdated from '@/hooks/use-history-update'; const toolbarList1 = [ { @@ -24,6 +26,15 @@ const toolbarList1 = [ icon: , toolTip: 'Select', disable: false, + callback: () => { + window.dispatchEvent( + new CustomEvent('affine.switch-mouse-mode', { + detail: { + type: 'default', + }, + }) + ); + }, }, { flavor: 'text', @@ -34,8 +45,19 @@ const toolbarList1 = [ { flavor: 'shape', icon: , - toolTip: 'Shape (coming soon)', - disable: true, + toolTip: 'Shape', + disable: false, + callback: () => { + window.dispatchEvent( + new CustomEvent('affine.switch-mouse-mode', { + detail: { + type: 'shape', + color: 'black', + shape: 'rectangle', + }, + }) + ); + }, }, { flavor: 'sticky', @@ -57,34 +79,19 @@ const toolbarList1 = [ disable: true, }, ]; -const toolbarList2 = [ - { - flavor: 'undo', - icon: , - toolTip: 'Undo', - disable: false, - }, - { - flavor: 'redo', - icon: , - toolTip: 'Redo', - disable: false, - }, -]; const UndoRedo = () => { const [canUndo, setCanUndo] = useState(false); const [canRedo, setCanRedo] = useState(false); - const { editor } = useEditor(); - useEffect(() => { - if (!editor) return; - const { page } = editor; + const { currentPage } = useAppState(); + const onHistoryUpdated = useHistoryUpdated(); - page.signals.historyUpdated.on(() => { + useEffect(() => { + onHistoryUpdated(page => { setCanUndo(page.canUndo); setCanRedo(page.canRedo); }); - }, [editor]); + }, [onHistoryUpdated]); return ( @@ -92,7 +99,7 @@ const UndoRedo = () => { { - editor?.page?.undo(); + currentPage?.undo(); }} > @@ -102,7 +109,7 @@ const UndoRedo = () => { { - editor?.page?.redo(); + currentPage?.redo(); }} > @@ -113,7 +120,7 @@ const UndoRedo = () => { }; export const EdgelessToolbar = () => { - const { mode } = useEditor(); + const { mode } = useCurrentPageMeta() || {}; return ( { mountOnEnter unmountOnExit > - + - {toolbarList1.map(({ icon, toolTip, flavor, disable }, index) => { - return ( - - { - console.log('flavor', flavor); - }} - > - {icon} - - - ); - })} + {toolbarList1.map( + ({ icon, toolTip, flavor, disable, callback }, index) => { + return ( + + { + console.log('click toolbar button:', flavor); + callback?.(); + }} + > + {icon} + + + ); + } + )} diff --git a/packages/app/src/components/edgeless-toolbar/style.ts b/packages/app/src/components/edgeless-toolbar/style.ts index 0b3a6d0b82..ff925808e7 100644 --- a/packages/app/src/components/edgeless-toolbar/style.ts +++ b/packages/app/src/components/edgeless-toolbar/style.ts @@ -2,12 +2,12 @@ import { styled, displayFlex } from '@/styles'; export const StyledEdgelessToolbar = styled.div(({ theme }) => ({ height: '320px', - position: 'fixed', + position: 'absolute', left: '12px', top: 0, bottom: 0, margin: 'auto', - zIndex: theme.zIndex.modal, + zIndex: theme.zIndex.modal - 1, })); export const StyledToolbarWrapper = styled.div(({ theme }) => ({ diff --git a/packages/app/src/components/editor-mode-switch/index.tsx b/packages/app/src/components/editor-mode-switch/index.tsx index d3d6777c9f..8a219e4757 100644 --- a/packages/app/src/components/editor-mode-switch/index.tsx +++ b/packages/app/src/components/editor-mode-switch/index.tsx @@ -11,9 +11,10 @@ import type { AnimateRadioProps, AnimateRadioItemProps, } from './type'; -import { useTheme } from '@/styles'; +import { useTheme } from '@/providers/themeProvider'; import { EdgelessIcon, PaperIcon } from './icons'; -import { useEditor } from '@/components/editor-provider'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; +import { usePageHelper } from '@/hooks/use-page-helper'; const PaperItem = ({ active }: { active?: boolean }) => { const { @@ -66,7 +67,9 @@ export const EditorModeSwitch = ({ style = {}, }: AnimateRadioProps) => { const { mode: themeMode } = useTheme(); - const { mode, setMode } = useEditor(); + const { changePageMode } = usePageHelper(); + const { trash, mode = 'page', id = '' } = useCurrentPageMeta() || {}; + const modifyRadioItemStatus = (): RadioItemStatus => { return { left: isHover @@ -99,6 +102,7 @@ export const EditorModeSwitch = ({ data-testid="editor-mode-switcher" shrink={!isHover} style={style} + disabled={!!trash} > { - setMode('page'); + changePageMode(id, 'page'); }} onMouseEnter={() => { setRadioItemStatus({ @@ -123,11 +127,12 @@ export const EditorModeSwitch = ({ } active={mode === 'edgeless'} status={radioItemStatus.right} onClick={() => { - setMode('edgeless'); + changePageMode(id, 'edgeless'); }} onMouseEnter={() => { setRadioItemStatus({ diff --git a/packages/app/src/components/editor-mode-switch/style.ts b/packages/app/src/components/editor-mode-switch/style.ts index 82c455c244..d17f65bd19 100644 --- a/packages/app/src/components/editor-mode-switch/style.ts +++ b/packages/app/src/components/editor-mode-switch/style.ts @@ -1,45 +1,47 @@ import { displayFlex, keyframes, styled } from '@/styles'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import spring, { toString } from 'css-spring'; import type { ItemStatus } from './type'; const ANIMATE_DURATION = 500; -export const StyledAnimateRadioContainer = styled('div')<{ shrink: boolean }>( - ({ shrink, theme }) => { - const animateScaleStretch = keyframes`${toString( - spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) - )}`; - const animateScaleShrink = keyframes( - `${toString( - spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) - )}` - ); - const shrinkStyle = shrink - ? { - animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`, - background: 'transparent', - } - : { - animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`, - }; +export const StyledAnimateRadioContainer = styled('div')<{ + shrink: boolean; + disabled: boolean; +}>(({ shrink, theme, disabled }) => { + const animateScaleStretch = keyframes`${toString( + spring({ width: '36px' }, { width: '160px' }, { preset: 'gentle' }) + )}`; + const animateScaleShrink = keyframes( + `${toString( + spring({ width: '160px' }, { width: '36px' }, { preset: 'gentle' }) + )}` + ); + const shrinkStyle = shrink + ? { + animation: `${animateScaleShrink} ${ANIMATE_DURATION}ms forwards`, + background: 'transparent', + } + : { + animation: `${animateScaleStretch} ${ANIMATE_DURATION}ms forwards`, + }; - return { - height: '36px', - borderRadius: '18px', - background: theme.colors.hoverBackground, - position: 'relative', - display: 'flex', - transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`, - border: '1px solid transparent', + return { + height: '36px', + borderRadius: '18px', + background: disabled ? 'transparent' : theme.colors.hoverBackground, + position: 'relative', + display: 'flex', + transition: `background ${ANIMATE_DURATION}ms, border ${ANIMATE_DURATION}ms`, + border: '1px solid transparent', - ...shrinkStyle, - ':hover': { - border: `1px solid ${theme.colors.primaryColor}`, - }, - }; - } -); + ...(disabled ? { pointerEvents: 'none' } : shrinkStyle), + ':hover': { + border: disabled ? '' : `1px solid ${theme.colors.primaryColor}`, + }, + }; +}); export const StyledMiddleLine = styled('div')<{ hidden: boolean; diff --git a/packages/app/src/components/editor-provider.tsx b/packages/app/src/components/editor-provider.tsx deleted file mode 100644 index 52f04ceb3d..0000000000 --- a/packages/app/src/components/editor-provider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import type { EditorContainer } from '@blocksuite/editor'; - -import { createContext, useContext, useEffect, useState } from 'react'; -import type { PropsWithChildren } from 'react'; - -type EditorContextValue = { - editor: EditorContainer | null; - mode: EditorContainer['mode']; - setEditor: (editor: EditorContainer) => void; - setMode: (mode: EditorContainer['mode']) => void; -}; - -type EditorContextProps = PropsWithChildren<{}>; - -export const EditorContext = createContext({ - editor: null, - mode: 'page', - setEditor: () => {}, - setMode: () => {}, -}); - -export const useEditor = () => useContext(EditorContext); - -export const EditorProvider = ({ - children, -}: PropsWithChildren) => { - const [editor, setEditor] = useState(null); - const [mode, setMode] = useState('page'); - - useEffect(() => { - const event = new CustomEvent('affine.switch-mode', { detail: mode }); - window.dispatchEvent(event); - }, [mode]); - - return ( - - {children} - - ); -}; - -export default EditorProvider; diff --git a/packages/app/src/components/editor.tsx b/packages/app/src/components/editor.tsx deleted file mode 100644 index 11c29a78a8..0000000000 --- a/packages/app/src/components/editor.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { useEditor } from '@/components/editor-provider'; -import '@blocksuite/blocks'; -import '@blocksuite/blocks/style'; -import type { EditorContainer } from '@blocksuite/editor'; -import { createEditor, BlockSchema } from '@blocksuite/editor'; -import { Workspace } from '@blocksuite/store'; -import { forwardRef, Suspense, useEffect, useRef } from 'react'; -import pkg from '../../package.json'; -import exampleMarkdown from './example-markdown'; - -// eslint-disable-next-line react/display-name -const BlockSuiteEditor = forwardRef(({}, ref) => { - const containerElement = useRef(null); - useEffect(() => { - if (!containerElement.current) { - return; - } - const workspace = new Workspace({}); - const page = workspace.createPage('page0').register(BlockSchema); - const editor = createEditor(page); - containerElement.current.appendChild(editor); - if (ref) { - if ('current' in ref) { - ref.current = editor; - } else { - ref(editor); - } - } - return () => { - editor.remove(); - }; - }, [ref]); - return
; -}); - -export const Editor = () => { - const editorRef = useRef(null); - const { setEditor } = useEditor(); - useEffect(() => { - if (!editorRef.current) { - return; - } - setEditor(editorRef.current); - const { page } = editorRef.current as EditorContainer; - const pageId = page.addBlock({ - flavour: 'affine:page', - title: 'Welcome to the AFFiNE Alpha', - }); - const groupId = page.addBlock({ flavour: 'affine:group' }, pageId); - editorRef.current.clipboard.importMarkdown(exampleMarkdown, `${groupId}`); - page.resetHistory(); - }, [setEditor]); - - useEffect(() => { - const version = pkg.dependencies['@blocksuite/editor'].substring(1); - console.log(`BlockSuite live demo ${version}`); - }, []); - - return ( - Error!
}> - - - ); -}; - -declare global { - interface HTMLElementTagNameMap { - 'editor-container': EditorContainer; - } - - namespace JSX { - interface IntrinsicElements { - // TODO fix types on react - 'editor-container': EditorContainer; - } - } -} - -export default Editor; diff --git a/packages/app/src/components/example-markdown.ts b/packages/app/src/components/example-markdown.ts deleted file mode 100644 index c41368cc3c..0000000000 --- a/packages/app/src/components/example-markdown.ts +++ /dev/null @@ -1,62 +0,0 @@ -const exampleMarkdown = `The AFFiNE Alpha is here! You can also view our [Official Website](https://affine.pro/)! - -**What's different in AFFiNE Alpha?** - -1. A much ~smoother editing~ experience, with much ~greater stability~; -2. More complete ~Markdown~ support and improved ~keyboard shortcuts~; -3. New features such as ~dark mode~; - * **Switch between view styles using the** ☀ **and** 🌙. -4. ~Clean and modern UI/UX~ design. - -**Looking for Markdown syntax or keyboard shortcuts?** - -* Find the (?) in the bottom right, then the ️⌨️, to view a full list of \`Keyboard Shortcuts\` - -**Have an enjoyable editing experience !!!** 😃 - -Have some feedback or just want to get in touch? Use the (?), then 🎧 to get in touch and join our communities. - -**Still in development** - -There are also some things you should consider about this AFFiNE Alpha including some ~limitations~: - -* Single page editing - currently editing multiple docs/pages is not supported; -* Changes are not automatically stored, to save changes you should export your data. Options can be found by going to the top right and finding the \`⋮\` icon; -* Without an import/open feature you are still able to access your data by copying it back in. - -**Keyboard Shortcuts:** - -1. Undo: \`⌘+Z\` or \`Ctrl+Z\` -2. Redo: \`⌘+⇧+Z\` or \`Ctrl+Shift+Z\` -3. Bold: \`⌘+B\` or \`Ctrl+B\` -4. Italic: \`⌘+I\` or \`Ctrl+I\` -5. Underline: \`⌘+U\` or \`Ctrl+U\` -6. Strikethrough: \`⌘+⇧+S\` or \`Ctrl+Shift+S\` -7. Inline code: \`⌘+E\` or \`Ctrl+E\` -8. Link: \`⌘+K\` or \`Ctrl+K\` -9. Body text: \`⌘+⌥+0\` or \`Ctrl+Shift+0\` -10. Heading 1: \`⌘+⌥+1\` or \`Ctrl+Shift+1\` -11. Heading 2: \`⌘+⌥+2\` or \`Ctrl+Shift+2\` -12. Heading 3: \`⌘+⌥+3\` or \`Ctrl+Shift+3\` -13. Heading 4: \`⌘+⌥+4\` or \`Ctrl+Shift+4\` -14. Heading 5: \`⌘+⌥+5\` or \`Ctrl+Shift+5\` -15. Heading 6: \`⌘+⌥+6\` or \`Ctrl+Shift+6\` -16. Increase indent: \`Tab\` -17. Reduce indent: \`⇧+Tab\` or \`Shift+Tab\` - -**Markdown Syntax:** - -1. Bold: \`**text**\` -2. Italic: \`*text*\` -3. Underline: \`~text~\` -4. Strikethrough: \`~~text~~\` -5. Inline code: \`\` \`text\` \`\` -6. Heading 1: \`# text\` -7. Heading 2: \`## text\` -8. Heading 3: \`### text\` -9. Heading 4: \`#### text\` -10. Heading 5: \`##### text\` -11. Heading 6: \`###### text\` -`; - -export default exampleMarkdown; diff --git a/packages/app/src/components/faq/index.tsx b/packages/app/src/components/faq/index.tsx deleted file mode 100644 index ad356ff380..0000000000 --- a/packages/app/src/components/faq/index.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useState } from 'react'; -import { - StyledFAQ, - StyledIconWrapper, - StyledFAQWrapper, - StyledTransformIcon, -} from './style'; -import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons'; -import Grow from '@mui/material/Grow'; -import { Tooltip } from '@/ui/tooltip'; -import { useEditor } from '@/components/editor-provider'; -import { useModal } from '@/components/global-modal-provider'; -import { useTheme } from '@/styles'; - -export const FAQ = () => { - const [showContent, setShowContent] = useState(false); - const { mode } = useTheme(); - const { mode: editorMode } = useEditor(); - const { shortcutsModalHandler, contactModalHandler } = useModal(); - const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless'; - - return ( - <> - { - setShowContent(true); - }} - onMouseLeave={() => { - setShowContent(false); - }} - > - - - - { - setShowContent(false); - contactModalHandler(true); - }} - > - - - - - { - setShowContent(false); - shortcutsModalHandler(true); - }} - > - - - - - - -
- - - - - - -
-
- - ); -}; - -const routesLIst: any = [ - { - path: '/', - children: [ - { - element: , - }, - ], - }, -]; diff --git a/packages/app/src/components/file-upload/index.tsx b/packages/app/src/components/file-upload/index.tsx new file mode 100644 index 0000000000..25d91fb06d --- /dev/null +++ b/packages/app/src/components/file-upload/index.tsx @@ -0,0 +1,47 @@ +import { Button } from '@/ui/button'; +import { FC, useRef, ChangeEvent, ReactElement } from 'react'; +import { styled } from '@/styles'; +interface Props { + uploadType?: string; + children?: ReactElement; + accept?: string; + fileChange: (file: File) => void; +} +export const Upload: FC = props => { + const { fileChange, accept } = props; + const input_ref = useRef(null); + const _chooseFile = () => { + if (input_ref.current) { + input_ref.current.click(); + } + }; + const _handleInputChange = (e: ChangeEvent) => { + const files = e.target.files; + if (!files) { + return; + } + const file = files[0]; + fileChange(file); + if (input_ref.current) { + input_ref.current.value = ''; + } + }; + return ( + + {props.children ?? } + + + ); +}; + +const UploadStyle = styled('div')(() => { + return { + display: 'inline-block', + }; +}); diff --git a/packages/app/src/components/global-modal-provider.tsx b/packages/app/src/components/global-modal-provider.tsx deleted file mode 100644 index fe4851f197..0000000000 --- a/packages/app/src/components/global-modal-provider.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { createContext, useContext, useState } from 'react'; -import type { PropsWithChildren } from 'react'; -import ShortcutsModal from './shortcuts-modal'; -import ContactModal from '@/components/contact-modal'; - -type ModalContextValue = { - shortcutsModalHandler: (visible: boolean) => void; - contactModalHandler: (visible: boolean) => void; -}; -type ModalContextProps = PropsWithChildren<{}>; - -export const ModalContext = createContext({ - shortcutsModalHandler: () => {}, - contactModalHandler: () => {}, -}); - -export const useModal = () => useContext(ModalContext); - -export const ModalProvider = ({ - children, -}: PropsWithChildren) => { - const [openContactModal, setOpenContactModal] = useState(false); - const [openShortcutsModal, setOpenShortcutsModal] = useState(false); - - return ( - { - setOpenShortcutsModal(visible); - }, - contactModalHandler: visible => { - setOpenContactModal(visible); - }, - }} - > - { - setOpenContactModal(false); - }} - > - { - setOpenShortcutsModal(false); - }} - > - {children} - - ); -}; - -export default ModalProvider; diff --git a/packages/app/src/components/header/editor-header.tsx b/packages/app/src/components/header/editor-header.tsx new file mode 100644 index 0000000000..c1b52ec464 --- /dev/null +++ b/packages/app/src/components/header/editor-header.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from 'react'; +import { + StyledSearchArrowWrapper, + StyledSwitchWrapper, + StyledTitle, + StyledTitleWrapper, +} from './styles'; +import { Content } from '@/ui/layout'; +import { useAppState } from '@/providers/app-state-provider/context'; +import EditorModeSwitch from '@/components/editor-mode-switch'; +import QuickSearchButton from './quick-search-button'; +import Header from './header'; +import usePropsUpdated from '@/hooks/use-props-updated'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; + +export const EditorHeader = () => { + const [title, setTitle] = useState(''); + const [isHover, setIsHover] = useState(false); + const { editor } = useAppState(); + const { trash: isTrash = false } = useCurrentPageMeta() || {}; + const onPropsUpdated = usePropsUpdated(); + + useEffect(() => { + onPropsUpdated(editor => { + setTitle(editor.model?.title || 'Untitled'); + }); + }, [onPropsUpdated]); + + useEffect(() => { + setTimeout(() => { + // If first time in, need to wait for editor to be inserted into DOM + setTitle(editor?.model?.title || 'Untitled'); + }, 300); + }, [editor]); + + return ( +
+ {title && ( + { + if (isTrash) return; + + setIsHover(true); + }} + onMouseLeave={() => { + if (isTrash) return; + + setIsHover(false); + }} + > + + + + + {title} + + + + + + )} +
+ ); +}; + +export default EditorHeader; diff --git a/packages/app/src/components/header/header-right-items/editor-option-menu.tsx b/packages/app/src/components/header/header-right-items/editor-option-menu.tsx new file mode 100644 index 0000000000..24a07245ee --- /dev/null +++ b/packages/app/src/components/header/header-right-items/editor-option-menu.tsx @@ -0,0 +1,111 @@ +import { Menu, MenuItem } from '@/ui/menu'; +import { IconButton } from '@/ui/button'; +import { + EdgelessIcon, + ExportIcon, + ExportToHtmlIcon, + ExportToMarkdownIcon, + FavouritedIcon, + FavouritesIcon, + MoreVerticalIcon, + PaperIcon, + TrashIcon, +} from '@blocksuite/icons'; +import { useAppState } from '@/providers/app-state-provider'; +import { usePageHelper } from '@/hooks/use-page-helper'; +import { useConfirm } from '@/providers/confirm-provider'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; +import { toast } from '@/ui/toast'; + +const PopoverContent = () => { + const { editor } = useAppState(); + const { toggleFavoritePage, toggleDeletePage } = usePageHelper(); + const { changePageMode } = usePageHelper(); + const { confirm } = useConfirm(); + const { + mode = 'page', + id = '', + favorite = false, + title = '', + } = useCurrentPageMeta() || {}; + + return ( + <> + { + toggleFavoritePage(id); + toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites'); + }} + icon={favorite ? : } + > + {favorite ? 'Remove' : 'Add'} to favourites + + : } + data-testid="editor-option-menu-edgeless" + onClick={() => { + changePageMode(id, mode === 'page' ? 'edgeless' : 'page'); + }} + > + Convert to {mode === 'page' ? 'Edgeless' : 'Page'} + + + { + editor && editor.contentParser.onExportHtml(); + }} + icon={} + > + Export to HTML + + { + editor && editor.contentParser.onExportMarkdown(); + }} + icon={} + > + Export to Markdown + + + } + > + } isDir={true}> + Export + + + { + confirm({ + title: 'Delete page?', + content: `${title || 'Untitled'} will be moved to Trash`, + confirmText: 'Delete', + confirmType: 'danger', + }).then(confirm => { + confirm && toggleDeletePage(id); + toast('Moved to Trash'); + }); + }} + icon={} + > + Delete + + + ); +}; + +export const EditorOptionMenu = () => { + return ( + } placement="bottom-end"> + + + + + ); +}; + +export default EditorOptionMenu; diff --git a/packages/app/src/components/header/header-right-items/sync-user.tsx b/packages/app/src/components/header/header-right-items/sync-user.tsx new file mode 100644 index 0000000000..3d7fba5299 --- /dev/null +++ b/packages/app/src/components/header/header-right-items/sync-user.tsx @@ -0,0 +1,25 @@ +import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons'; +import { useModal } from '@/providers/global-modal-provider'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { IconButton } from '@/ui/button'; + +export const SyncUser = () => { + const { triggerLoginModal } = useModal(); + const appState = useAppState(); + + return appState.user ? ( + + + + ) : ( + + + + ); +}; + +export default SyncUser; diff --git a/packages/app/src/components/theme-mode-switch/icons.tsx b/packages/app/src/components/header/header-right-items/theme-mode-switch/icons.tsx similarity index 100% rename from packages/app/src/components/theme-mode-switch/icons.tsx rename to packages/app/src/components/header/header-right-items/theme-mode-switch/icons.tsx diff --git a/packages/app/src/components/theme-mode-switch/index.tsx b/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx similarity index 95% rename from packages/app/src/components/theme-mode-switch/index.tsx rename to packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx index ad3c32dd3f..20f6c86bdb 100644 --- a/packages/app/src/components/theme-mode-switch/index.tsx +++ b/packages/app/src/components/header/header-right-items/theme-mode-switch/index.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { useTheme } from '@/styles'; +import { useTheme } from '@/providers/themeProvider'; import { MoonIcon, SunIcon } from './icons'; import { StyledThemeModeSwitch, StyledSwitchItem } from './style'; diff --git a/packages/app/src/components/theme-mode-switch/style.ts b/packages/app/src/components/header/header-right-items/theme-mode-switch/style.ts similarity index 96% rename from packages/app/src/components/theme-mode-switch/style.ts rename to packages/app/src/components/header/header-right-items/theme-mode-switch/style.ts index 7f90930e67..59d82211e8 100644 --- a/packages/app/src/components/theme-mode-switch/style.ts +++ b/packages/app/src/components/header/header-right-items/theme-mode-switch/style.ts @@ -1,5 +1,6 @@ import { displayFlex, keyframes, styled } from '@/styles'; import { CSSProperties } from 'react'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import spring, { toString } from 'css-spring'; diff --git a/packages/app/src/components/header/header-right-items/trash-button-group.tsx b/packages/app/src/components/header/header-right-items/trash-button-group.tsx new file mode 100644 index 0000000000..02345e9680 --- /dev/null +++ b/packages/app/src/components/header/header-right-items/trash-button-group.tsx @@ -0,0 +1,53 @@ +import { Button } from '@/ui/button'; +import { usePageHelper } from '@/hooks/use-page-helper'; +import { useAppState } from '@/providers/app-state-provider'; +import { useConfirm } from '@/providers/confirm-provider'; +import { useRouter } from 'next/router'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; + +export const TrashButtonGroup = () => { + const { permanentlyDeletePage } = usePageHelper(); + const { currentWorkspaceId } = useAppState(); + const { toggleDeletePage } = usePageHelper(); + const { confirm } = useConfirm(); + const router = useRouter(); + const { id = '' } = useCurrentPageMeta() || {}; + + return ( + <> + + + + ); +}; + +export default TrashButtonGroup; diff --git a/packages/app/src/components/header/header.tsx b/packages/app/src/components/header/header.tsx new file mode 100644 index 0000000000..21d99f01ce --- /dev/null +++ b/packages/app/src/components/header/header.tsx @@ -0,0 +1,71 @@ +import React, { PropsWithChildren, ReactNode, useState } from 'react'; +import { + StyledHeader, + StyledHeaderRightSide, + StyledHeaderContainer, + StyledBrowserWarning, + StyledCloseButton, +} from './styles'; +import CloseIcon from '@mui/icons-material/Close'; +import { getWarningMessage, shouldShowWarning } from './utils'; +import EditorOptionMenu from './header-right-items/editor-option-menu'; +import TrashButtonGroup from './header-right-items/trash-button-group'; +import ThemeModeSwitch from './header-right-items/theme-mode-switch'; +// import SyncUser from './header-right-items/sync-user'; + +const BrowserWarning = ({ + show, + onClose, +}: { + show: boolean; + onClose: () => void; +}) => { + return ( + + {getWarningMessage()} + + + + + ); +}; + +type HeaderRightItemNames = + | 'editorOptionMenu' + | 'trashButtonGroup' + | 'themeModeSwitch' + | 'syncUser'; + +const HeaderRightItems: Record = { + editorOptionMenu: , + trashButtonGroup: , + themeModeSwitch: , + syncUser: null, +}; + +export const Header = ({ + rightItems = ['syncUser'], + children, +}: PropsWithChildren<{ rightItems?: HeaderRightItemNames[] }>) => { + const [showWarning, setShowWarning] = useState(shouldShowWarning()); + return ( + + { + setShowWarning(false); + }} + /> + + {children} + + {rightItems.map(itemName => { + return HeaderRightItems[itemName]; + })} + + + + ); +}; + +export default Header; diff --git a/packages/app/src/components/header/index.tsx b/packages/app/src/components/header/index.tsx new file mode 100644 index 0000000000..d31b8f7abb --- /dev/null +++ b/packages/app/src/components/header/index.tsx @@ -0,0 +1,3 @@ +export * from './header'; +export * from './editor-header'; +export * from './page-list-header'; diff --git a/packages/app/src/components/header/page-list-header.tsx b/packages/app/src/components/header/page-list-header.tsx new file mode 100644 index 0000000000..452061fbd4 --- /dev/null +++ b/packages/app/src/components/header/page-list-header.tsx @@ -0,0 +1,21 @@ +import { PropsWithChildren, ReactNode } from 'react'; +import Header from './header'; +import { StyledPageListTittleWrapper } from './styles'; +import QuickSearchButton from './quick-search-button'; + +export type PageListHeaderProps = PropsWithChildren<{ + icon?: ReactNode; +}>; +export const PageListHeader = ({ icon, children }: PageListHeaderProps) => { + return ( +
+ + {icon} + {children} + + +
+ ); +}; + +export default PageListHeader; diff --git a/packages/app/src/components/header/quick-search-button.tsx b/packages/app/src/components/header/quick-search-button.tsx new file mode 100644 index 0000000000..a08c61bf65 --- /dev/null +++ b/packages/app/src/components/header/quick-search-button.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import { IconButton, IconButtonProps } from '@/ui/button'; +import { Tooltip } from '@/ui/tooltip'; +import { ArrowDownIcon } from '@blocksuite/icons'; +import { useModal } from '@/providers/global-modal-provider'; + +export const QuickSearchButton = ({ + onClick, + ...props +}: Omit) => { + const { triggerQuickSearchModal } = useModal(); + + return ( + + { + onClick?.(e); + triggerQuickSearchModal(); + }} + > + + + + ); +}; + +export default QuickSearchButton; diff --git a/packages/app/src/components/Header/styles.ts b/packages/app/src/components/header/styles.ts similarity index 57% rename from packages/app/src/components/Header/styles.ts rename to packages/app/src/components/header/styles.ts index 3e3978ca3a..1deb826faa 100644 --- a/packages/app/src/components/Header/styles.ts +++ b/packages/app/src/components/header/styles.ts @@ -1,5 +1,4 @@ import { displayFlex, styled } from '@/styles'; -import { MenuItem } from '@/ui/menu'; export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>( ({ hasWarning }) => { @@ -10,14 +9,14 @@ export const StyledHeaderContainer = styled.div<{ hasWarning: boolean }>( } ); export const StyledHeader = styled.div<{ hasWarning: boolean }>( - ({ hasWarning, theme }) => { + ({ hasWarning }) => { return { height: '60px', - width: '100vw', - ...displayFlex('space-between', 'center'), + width: '100%', + ...displayFlex('flex-end', 'center'), background: 'var(--affine-page-background)', transition: 'background-color 0.5s', - position: 'fixed', + position: 'absolute', left: '0', top: hasWarning ? '36px' : '0', padding: '0 22px', @@ -41,20 +40,10 @@ export const StyledTitle = styled('div')(({ theme }) => ({ export const StyledTitleWrapper = styled('div')({ maxWidth: '720px', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', + height: '100%', position: 'relative', -}); - -export const StyledLogo = styled('div')(({ theme }) => ({ - color: theme.colors.primaryColor, - width: '60px', - height: '60px', - cursor: 'pointer', - marginLeft: '-22px', ...displayFlex('center', 'center'), -})); +}); export const StyledHeaderRightSide = styled('div')({ height: '100%', @@ -62,54 +51,23 @@ export const StyledHeaderRightSide = styled('div')({ alignItems: 'center', }); -export const StyledMenuItemWrapper = styled.div(({ theme }) => { - return { - height: '32px', - position: 'relative', - cursor: 'pointer', - ...displayFlex('flex-start', 'center'), - svg: { - width: '16px', - height: '16px', - marginRight: '14px', - }, - 'svg:nth-child(2)': { - position: 'absolute', - right: 0, - top: 0, - bottom: 0, - margin: 'auto', - }, - }; -}); - -export const IconButton = styled('div')(({ theme }) => { - return { - width: '32px', - height: '32px', - ...displayFlex('center', 'center'), - color: theme.colors.iconColor, - borderRadius: '5px', - ':hover': { - color: theme.colors.primaryColor, - background: theme.colors.hoverBackground, - }, - }; -}); - -export const StyledBrowserWarning = styled.div(({ theme }) => { - return { - backgroundColor: theme.colors.warningBackground, - color: theme.colors.warningColor, - height: '36px', - width: '100vw', - fontSize: theme.font.sm, - position: 'fixed', - left: '0', - top: '0', - ...displayFlex('center', 'center'), - }; -}); +export const StyledBrowserWarning = styled.div<{ show: boolean }>( + ({ theme, show }) => { + return { + backgroundColor: theme.colors.warningBackground, + color: theme.colors.warningColor, + height: '36px', + width: '100vw', + fontSize: theme.font.sm, + position: 'fixed', + left: '0', + top: '0', + display: show ? 'flex' : 'none', + justifyContent: 'center', + alignItems: 'center', + }; + } +); export const StyledCloseButton = styled.div(({ theme }) => { return { @@ -130,3 +88,36 @@ export const StyledCloseButton = styled.div(({ theme }) => { }, }; }); + +export const StyledSwitchWrapper = styled.div(() => { + return { + position: 'absolute', + right: '100%', + top: 0, + bottom: 0, + margin: 'auto', + ...displayFlex('center', 'center'), + }; +}); + +export const StyledSearchArrowWrapper = styled.div(() => { + return { + position: 'absolute', + left: 'calc(100% + 4px)', + top: 0, + bottom: 0, + margin: 'auto', + ...displayFlex('center', 'center'), + }; +}); + +export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => { + return { + fontSize: theme.font.sm, + color: theme.colors.textColor, + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + }; +}); diff --git a/packages/app/src/components/Header/utils.tsx b/packages/app/src/components/header/utils.tsx similarity index 100% rename from packages/app/src/components/Header/utils.tsx rename to packages/app/src/components/header/utils.tsx diff --git a/packages/app/src/components/faq/icons.tsx b/packages/app/src/components/help-island/icons.tsx similarity index 100% rename from packages/app/src/components/faq/icons.tsx rename to packages/app/src/components/help-island/icons.tsx diff --git a/packages/app/src/components/help-island/index.tsx b/packages/app/src/components/help-island/index.tsx new file mode 100644 index 0000000000..351b63289e --- /dev/null +++ b/packages/app/src/components/help-island/index.tsx @@ -0,0 +1,86 @@ +import { useState } from 'react'; +import { + StyledIsland, + StyledIconWrapper, + StyledIslandWrapper, + StyledTransformIcon, +} from './style'; +import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './icons'; +import Grow from '@mui/material/Grow'; +import { Tooltip } from '@/ui/tooltip'; +import { useModal } from '@/providers/global-modal-provider'; +import { useTheme } from '@/providers/themeProvider'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; +export type IslandItemNames = 'contact' | 'shortcuts'; +export const HelpIsland = ({ + showList = ['contact', 'shortcuts'], +}: { + showList?: IslandItemNames[]; +}) => { + const [showContent, setShowContent] = useState(false); + const { mode } = useTheme(); + const { mode: editorMode } = useCurrentPageMeta() || {}; + const { triggerShortcutsModal, triggerContactModal } = useModal(); + const isEdgelessDark = mode === 'dark' && editorMode === 'edgeless'; + + return ( + <> + { + setShowContent(true); + }} + onMouseLeave={() => { + setShowContent(false); + }} + > + + + {showList.includes('contact') && ( + + { + setShowContent(false); + triggerContactModal(); + }} + > + + + + )} + {showList.includes('shortcuts') && ( + + { + setShowContent(false); + triggerShortcutsModal(); + }} + > + + + + )} + + + +
+ + + + + + +
+
+ + ); +}; + +export default HelpIsland; diff --git a/packages/app/src/components/faq/style.ts b/packages/app/src/components/help-island/style.ts similarity index 67% rename from packages/app/src/components/faq/style.ts rename to packages/app/src/components/help-island/style.ts index e39e6280c5..9373a53da6 100644 --- a/packages/app/src/components/faq/style.ts +++ b/packages/app/src/components/help-island/style.ts @@ -1,6 +1,6 @@ import { displayFlex, styled } from '@/styles'; -export const StyledFAQ = styled('div')(({ theme }) => { +export const StyledIsland = styled('div')(({ theme }) => { return { width: '32px', height: '32px', @@ -12,24 +12,24 @@ export const StyledFAQ = styled('div')(({ theme }) => { zIndex: theme.zIndex.popover, }; }); -export const StyledTransformIcon = styled.div<{ in: boolean }>( - ({ in: isIn, theme }) => ({ - height: '32px', - width: '32px', - borderRadius: '50%', - position: 'absolute', - left: '0', - right: '0', - bottom: '0', - top: '0', - margin: 'auto', - ...displayFlex('center', 'center'), - opacity: isIn ? 1 : 0, - backgroundColor: isIn - ? theme.colors.hoverBackground - : theme.colors.pageBackground, - }) -); +export const StyledTransformIcon = styled('div', { + shouldForwardProp: prop => prop !== 'in', +})<{ in: boolean }>(({ in: isIn, theme }) => ({ + height: '32px', + width: '32px', + borderRadius: '50%', + position: 'absolute', + left: '0', + right: '0', + bottom: '0', + top: '0', + margin: 'auto', + ...displayFlex('center', 'center'), + opacity: isIn ? 1 : 0, + backgroundColor: isIn + ? theme.colors.hoverBackground + : theme.colors.pageBackground, +})); export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>( ({ theme, isEdgelessDark }) => { return { @@ -57,7 +57,7 @@ export const StyledIconWrapper = styled('div')<{ isEdgelessDark: boolean }>( } ); -export const StyledFAQWrapper = styled('div')(({ theme }) => { +export const StyledIslandWrapper = styled('div')(({ theme }) => { return { position: 'absolute', bottom: '100%', diff --git a/packages/app/src/components/import/index.tsx b/packages/app/src/components/import/index.tsx new file mode 100644 index 0000000000..3667309a34 --- /dev/null +++ b/packages/app/src/components/import/index.tsx @@ -0,0 +1,65 @@ +import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; +import { StyledButtonWrapper, StyledTitle } from './styles'; +import { Button } from '@/ui/button'; +import { Wrapper, Content } from '@/ui/layout'; +import Loading from '@/components/loading'; +import { useEffect, useState } from 'react'; +type ImportModalProps = { + open: boolean; + onClose: () => void; +}; +export const ImportModal = ({ open, onClose }: ImportModalProps) => { + const [status, setStatus] = useState<'unImported' | 'importing'>('importing'); + + useEffect(() => { + if (status === 'importing') { + setTimeout(() => { + setStatus('unImported'); + }, 1500); + } + }, [status]); + + return ( + + + + Import + + {status === 'unImported' && ( + + + + + )} + + {status === 'importing' && ( + + + + OOOOPS! Sorry forgot to remind you that we are working on the + import function + + + )} + + + ); +}; + +export default ImportModal; diff --git a/packages/app/src/components/import/styles.ts b/packages/app/src/components/import/styles.ts new file mode 100644 index 0000000000..580d8a1535 --- /dev/null +++ b/packages/app/src/components/import/styles.ts @@ -0,0 +1,25 @@ +import { styled } from '@/styles'; + +export const StyledTitle = styled.div(({ theme }) => { + return { + fontSize: theme.font.h6, + fontWeight: 600, + textAlign: 'center', + marginTop: '45px', + color: theme.colors.popoverColor, + }; +}); + +export const StyledButtonWrapper = styled.div(() => { + return { + width: '280px', + margin: '24px auto 0', + button: { + display: 'block', + width: '100%', + ':not(:last-child)': { + marginBottom: '16px', + }, + }, + }; +}); diff --git a/packages/app/src/components/invite-members/index.tsx b/packages/app/src/components/invite-members/index.tsx new file mode 100644 index 0000000000..1cf5951237 --- /dev/null +++ b/packages/app/src/components/invite-members/index.tsx @@ -0,0 +1,251 @@ +import { EmailIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; +import { Button } from '@/ui/button'; +import Input from '@/ui/input'; +import { useState } from 'react'; +import { inviteMember, getUserByEmail } from '@affine/data-services'; +import { Avatar } from '@mui/material'; +interface LoginModalProps { + open: boolean; + onClose: () => void; + workspaceId: string; + onInviteSuccess: () => void; +} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const debounce = any>( + fn: T, + time?: number, + immediate?: boolean + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): ((...args: any) => any) => { + let timeoutId: null | number; + let defaultImmediate = immediate || false; + const delay = time || 300; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (...args: any) => { + if (defaultImmediate) { + fn.apply(this, args); + defaultImmediate = false; + return; + } + if (timeoutId) { + clearTimeout(timeoutId); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + timeoutId = setTimeout(() => { + fn.apply(this, args); + timeoutId = null; + }, delay); + }; +}; + +const gmailReg = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@gmail\.com$/; +export const InviteMembers = ({ + open, + onClose, + workspaceId, + onInviteSuccess, +}: LoginModalProps) => { + const [email, setEmail] = useState(''); + const [showMember, setShowMember] = useState(false); + const [showTip, setShowTip] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [userData, setUserData] = useState({}); + const inputChange = (value: string) => { + setEmail(value); + setShowMember(true); + if (gmailReg.test(value)) { + setShowTip(false); + debounce( + () => { + getUserByEmail({ + email: value, + workspace_id: workspaceId, + }).then(data => { + if (data?.name) { + setUserData(data); + setShowTip(false); + } + }); + }, + 300, + true + )(); + } else { + setShowTip(true); + } + }; + return ( +
+ + +
+ { + onClose(); + }} + /> +
+ + Invite members + + { + setShowMember(false); + }} + placeholder="Search mail (Gmail support only)" + > + {showMember ? ( + + {showTip ? ( + Non-Gmail is not supported + ) : ( + + {userData?.avatar_url ? ( + + ) : ( + + + + )} + {email} + {/*
invited
*/} +
+ )} +
+ ) : ( + <> + )} +
+
+
+ +
+
+
+
+ ); +}; + +const Header = styled('div')({ + position: 'relative', + height: '44px', +}); + +const Content = styled('div')({ + display: 'flex', + padding: '0 48px', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', +}); + +const ContentTitle = styled('h1')({ + fontSize: '20px', + lineHeight: '28px', + fontWeight: 600, + textAlign: 'center', + paddingBottom: '16px', +}); + +const Footer = styled('div')({ + height: '70px', + paddingLeft: '24px', + marginTop: '32px', + textAlign: 'center', +}); + +const InviteBox = styled('div')({ + position: 'relative', +}); + +const Members = styled('div')(({ theme }) => { + return { + position: 'absolute', + width: '100%', + background: theme.colors.pageBackground, + textAlign: 'left', + zIndex: 1, + borderRadius: '0px 10px 10px 10px', + height: '56px', + padding: '8px 12px', + input: { + '&::placeholder': { + color: theme.colors.placeHolderColor, + }, + }, + }; +}); + +const NoFind = styled('div')(({ theme }) => { + return { + color: theme.colors.iconColor, + fontSize: theme.font.sm, + lineHeight: '40px', + userSelect: 'none', + width: '100%', + }; +}); + +const Member = styled('div')(({ theme }) => { + return { + color: theme.colors.iconColor, + fontSize: theme.font.sm, + lineHeight: '40px', + userSelect: 'none', + display: 'flex', + }; +}); + +const MemberIcon = styled('div')(({ theme }) => { + return { + width: '40px', + height: '40px', + borderRadius: '50%', + color: theme.colors.primaryColor, + background: '#F5F5F5', + marginRight: '8px', + textAlign: 'center', + lineHeight: '45px', + // icon size + fontSize: '20px', + overflow: 'hidden', + img: { + width: '100%', + height: '100%', + }, + }; +}); + +const Email = styled('div')(({ theme }) => { + return { + flex: '1', + color: theme.colors.popoverColor, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); diff --git a/packages/app/src/components/loading/index.tsx b/packages/app/src/components/loading/index.tsx index a04cffefb5..d435470de9 100644 --- a/packages/app/src/components/loading/index.tsx +++ b/packages/app/src/components/loading/index.tsx @@ -1,14 +1,3 @@ -import { StyledLoading, StyledLoadingItem } from './styled'; - -export const Loading = () => { - return ( - - - - - - - ); -}; - +import Loading from './loading'; +export * from './page-loading'; export default Loading; diff --git a/packages/app/src/components/loading/loading.tsx b/packages/app/src/components/loading/loading.tsx new file mode 100644 index 0000000000..76ea2a25b6 --- /dev/null +++ b/packages/app/src/components/loading/loading.tsx @@ -0,0 +1,20 @@ +import { + StyledLoadingWrapper, + StyledLoading, + StyledLoadingItem, +} from './styled'; + +export const Loading = ({ size = 40 }: { size?: number }) => { + return ( + + + + + + + + + ); +}; + +export default Loading; diff --git a/packages/app/src/components/loading/page-loading.tsx b/packages/app/src/components/loading/page-loading.tsx new file mode 100644 index 0000000000..e4a802fa62 --- /dev/null +++ b/packages/app/src/components/loading/page-loading.tsx @@ -0,0 +1,31 @@ +import { styled } from '@/styles'; +import Loading from './loading'; + +// Used for the full page loading +const StyledLoadingContainer = styled('div')(() => { + return { + height: '100vh', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + color: '#6880FF', + h1: { + fontSize: '2em', + marginTop: '15px', + fontWeight: '600', + }, + }; +}); + +export const PageLoading = ({ text = 'Loading...' }: { text?: string }) => { + return ( + +
+ +

{text}

+
+
+ ); +}; + +export default PageLoading; diff --git a/packages/app/src/components/loading/styled.ts b/packages/app/src/components/loading/styled.ts index f267fc9a84..b98a8bfaa5 100644 --- a/packages/app/src/components/loading/styled.ts +++ b/packages/app/src/components/loading/styled.ts @@ -1,14 +1,19 @@ import { styled } from '@/styles'; // Inspired by https://codepen.io/graphilla/pen/rNvBMYY -const loadingItemSize = '40px'; +export const StyledLoadingWrapper = styled.div<{ size?: number }>( + ({ size = 40 }) => { + return { + width: size * 4, + height: size * 4, + position: 'relative', + }; + } +); export const StyledLoading = styled.div` - position: relative; - left: 50%; - transform: rotateX(55deg) rotateZ(-45deg) - translate(calc(${loadingItemSize} * -2), 0); - margin-bottom: calc(3 * ${loadingItemSize}); - + position: absolute; + left: 25%; + top: 25%; @keyframes slide { 0% { transform: translate(var(--sx), var(--sy)); @@ -21,24 +26,14 @@ export const StyledLoading = styled.div` transform: translate(var(--ex), var(--ey)); } } - @keyframes load { - 20% { - content: '.'; - } - 40% { - content: '..'; - } - 80%, - 100% { - content: '...'; - } - } `; -export const StyledLoadingItem = styled.div` +export const StyledLoadingItem = styled.div<{ size: number }>( + ({ size = 40 }) => { + return ` position: absolute; - width: ${loadingItemSize}; - height: ${loadingItemSize}; + width: ${size}px; + height: ${size}px; background: #9dacf9; animation: slide 0.9s cubic-bezier(0.65, 0.53, 0.59, 0.93) infinite; @@ -92,3 +87,5 @@ export const StyledLoadingItem = styled.div` --ey: -50%; } `; + } +); diff --git a/packages/app/src/components/login-modal/LoginOptionButton.tsx b/packages/app/src/components/login-modal/LoginOptionButton.tsx new file mode 100644 index 0000000000..298fbd86ba --- /dev/null +++ b/packages/app/src/components/login-modal/LoginOptionButton.tsx @@ -0,0 +1,118 @@ +import { signInWithGoogle } from '@affine/data-services'; +import { styled } from '@/styles'; +import { Button } from '@/ui/button'; +import { useModal } from '@/providers/global-modal-provider'; +import { GoogleIcon, StayLogOutIcon } from './icons'; + +export const GoogleLoginButton = () => { + const { triggerLoginModal } = useModal(); + return ( + { + signInWithGoogle() + .then(() => { + triggerLoginModal(); + }) + .catch(error => { + console.log('sign google error', error); + }); + }} + > + + + + + + Continue with Google + Set up an AFFiNE account to sync data + + + + ); +}; + +export const StayLogOutButton = () => { + return ( + + + + + + + Stay logged out + All changes are saved locally + + + + ); +}; + +const StyledGoogleButton = styled(Button)(() => { + return { + width: '361px', + height: '56px', + padding: '4px', + background: '#6880FF', + color: '#fff', + + '& > span': { + marginLeft: 0, + }, + + ':hover': { + background: '#516BF4', + color: '#fff', + }, + }; +}); + +const StyledStayLogOutButton = styled(Button)(() => { + return { + width: '361px', + height: '56px', + padding: '4px', + + '& > span': { + marginLeft: 0, + }, + + ':hover': { + borderColor: '#6880FF', + }, + }; +}); + +const ButtonWrapper = styled('div')({ + display: 'flex', + flexDirection: 'row', +}); + +const IconWrapper = styled('div')({ + width: '48px', + height: '48px', + flex: '0 48px', + borderRadius: '5px', + overflow: 'hidden', + marginRight: '12px', +}); + +const TextWrapper = styled('div')({ + flex: 1, + textAlign: 'left', +}); + +const Title = styled('h1')(() => { + return { + fontSize: '18px', + lineHeight: '26px', + fontWeight: 500, + }; +}); + +const Description = styled('p')(() => { + return { + fontSize: '16px', + lineHeight: '22px', + fontWeight: 400, + }; +}); diff --git a/packages/app/src/components/login-modal/google.svg b/packages/app/src/components/login-modal/google.svg new file mode 100644 index 0000000000..23af746fed --- /dev/null +++ b/packages/app/src/components/login-modal/google.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/app/src/components/login-modal/icons.tsx b/packages/app/src/components/login-modal/icons.tsx new file mode 100644 index 0000000000..a6c694c876 --- /dev/null +++ b/packages/app/src/components/login-modal/icons.tsx @@ -0,0 +1,42 @@ +import { CloudUnsyncedIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import GoogleSvg from './google.svg'; + +export const GoogleIcon = () => { + return ( + + + Google + + + ); +}; + +const GoogleIconWrapper = styled('div')(({ theme }) => ({ + width: '48px', + height: '48px', + background: theme.colors.pageBackground, + display: 'flex', + alignItems: 'center', + justifyContent: 'center', +})); + +export const StayLogOutIcon = () => { + return ( + + + + ); +}; + +const StayLogOutWrapper = styled('div')(({ theme }) => { + return { + width: '48px', + height: '48px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '24px', + background: theme.colors.hoverBackground, + }; +}); diff --git a/packages/app/src/components/login-modal/index.tsx b/packages/app/src/components/login-modal/index.tsx new file mode 100644 index 0000000000..f398383196 --- /dev/null +++ b/packages/app/src/components/login-modal/index.tsx @@ -0,0 +1,68 @@ +import { ResetIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; +import { TextButton } from '@/ui/button'; +import { GoogleLoginButton, StayLogOutButton } from './LoginOptionButton'; + +interface LoginModalProps { + open: boolean; + onClose: () => void; +} + +export const LoginModal = ({ open, onClose }: LoginModalProps) => { + return ( + + +
+ { + onClose(); + }} + /> +
+ + Currently not logged in + + + +
+ }>Clear local data +
+
+
+ ); +}; + +const Header = styled('div')({ + position: 'relative', + height: '44px', +}); + +const Content = styled('div')({ + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + gap: '16px', +}); + +const ContentTitle = styled('h1')({ + fontSize: '20px', + lineHeight: '28px', + fontWeight: 600, + textAlign: 'center', + paddingBottom: '16px', +}); + +const Footer = styled('div')({ + height: '70px', + paddingLeft: '24px', + marginTop: '32px', +}); + +const StyledResetIcon = styled(ResetIcon)({ + marginRight: '12px', + width: '20px', + height: '20px', +}); diff --git a/packages/app/src/components/mobile-modal/index.tsx b/packages/app/src/components/mobile-modal/index.tsx index 4c44359368..ff9796359b 100644 --- a/packages/app/src/components/mobile-modal/index.tsx +++ b/packages/app/src/components/mobile-modal/index.tsx @@ -1,14 +1,8 @@ import React, { useState } from 'react'; -import Modal from '@/ui/modal'; +import Modal, { ModalCloseButton, ModalWrapper } from '@/ui/modal'; import getIsMobile from '@/utils/get-is-mobile'; -import { - ModalWrapper, - StyledButton, - StyledCloseButton, - StyledContent, - StyledTitle, -} from './styles'; -import CloseIcon from '@mui/icons-material/Close'; +import { StyledButton, StyledContent, StyledTitle } from './styles'; +import bg from './bg.png'; export const MobileModal = () => { const [showModal, setShowModal] = useState(getIsMobile()); return ( @@ -18,14 +12,18 @@ export const MobileModal = () => { setShowModal(false); }} > - - + { setShowModal(false); }} - > - - + /> Ooops! diff --git a/packages/app/src/components/mobile-modal/styles.ts b/packages/app/src/components/mobile-modal/styles.ts index 14347f7375..40a8bcc375 100644 --- a/packages/app/src/components/mobile-modal/styles.ts +++ b/packages/app/src/components/mobile-modal/styles.ts @@ -1,38 +1,6 @@ import { displayFlex, styled } from '@/styles'; -import bg from './bg.png'; -export const ModalWrapper = styled.div(({ theme }) => { - return { - width: '348px', - height: '388px', - background: theme.colors.popoverBackground, - borderRadius: '28px', - position: 'relative', - backgroundImage: `url(${bg.src})`, - }; -}); - -export const StyledCloseButton = styled.div(({ theme }) => { - return { - width: '66px', - height: '66px', - color: theme.colors.iconColor, - cursor: 'pointer', - ...displayFlex('center', 'center'), - position: 'absolute', - right: '0', - top: '0', - - svg: { - width: '15px', - height: '15px', - position: 'relative', - zIndex: 1, - }, - }; -}); - -export const StyledTitle = styled.div(({ theme }) => { +export const StyledTitle = styled.div(() => { return { ...displayFlex('center', 'center'), fontSize: '20px', @@ -42,7 +10,7 @@ export const StyledTitle = styled.div(({ theme }) => { }; }); -export const StyledContent = styled.div(({ theme }) => { +export const StyledContent = styled.div(() => { return { padding: '0 40px', marginTop: '32px', diff --git a/packages/app/src/components/page-list/date-cell.tsx b/packages/app/src/components/page-list/date-cell.tsx new file mode 100644 index 0000000000..4ffc4e95cd --- /dev/null +++ b/packages/app/src/components/page-list/date-cell.tsx @@ -0,0 +1,27 @@ +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import dayjs from 'dayjs'; +import { PageMeta } from '@/providers/app-state-provider'; +import { TableCell } from '@/ui/table'; +import React from 'react'; + +dayjs.extend(localizedFormat); + +export const DateCell = ({ + pageMeta, + dateKey, + backupKey = '', +}: { + pageMeta: PageMeta; + dateKey: keyof PageMeta; + backupKey?: keyof PageMeta; +}) => { + // dayjs().format('L LT'); + const value = pageMeta[dateKey] ?? pageMeta[backupKey]; + return ( + + {value ? dayjs(value as string).format('YYYY-MM-DD HH:mm') : '--'} + + ); +}; + +export default DateCell; diff --git a/packages/app/src/components/page-list/empty.tsx b/packages/app/src/components/page-list/empty.tsx new file mode 100644 index 0000000000..ef13e1b117 --- /dev/null +++ b/packages/app/src/components/page-list/empty.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { Empty } from '@/ui/empty'; +export const PageListEmpty = () => { + return ( +
+ +

Tips: Click Add to Favourites/Trash and the page will appear here.

+

(Designer is grappling with designing)

+
+ ); +}; + +export default PageListEmpty; diff --git a/packages/app/src/components/page-list/index.tsx b/packages/app/src/components/page-list/index.tsx new file mode 100644 index 0000000000..f63fa39944 --- /dev/null +++ b/packages/app/src/components/page-list/index.tsx @@ -0,0 +1,145 @@ +import { PageMeta } from '@/providers/app-state-provider'; +import { + FavouritedIcon, + FavouritesIcon, + PaperIcon, + EdgelessIcon, +} from '@blocksuite/icons'; +import { + StyledTableContainer, + StyledTableRow, + StyledTitleLink, + StyledTitleWrapper, +} from './styles'; +import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table'; +import { OperationCell, TrashOperationCell } from './operation-cell'; +import Empty from './empty'; +import { Content } from '@/ui/layout'; +import React from 'react'; +import DateCell from '@/components/page-list/date-cell'; +import { IconButton } from '@/ui/button'; +import { Tooltip } from '@/ui/tooltip'; +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { toast } from '@/ui/toast'; +import { usePageHelper } from '@/hooks/use-page-helper'; +import { useTheme } from '@/providers/themeProvider'; + +const FavoriteTag = ({ + pageMeta: { favorite, id }, +}: { + pageMeta: PageMeta; +}) => { + const { toggleFavoritePage } = usePageHelper(); + const { theme } = useTheme(); + return ( + + { + e.stopPropagation(); + toggleFavoritePage(id); + toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites'); + }} + style={{ + color: favorite ? theme.colors.primaryColor : theme.colors.iconColor, + }} + className="favorite-button" + > + {favorite ? ( + + ) : ( + + )} + + + ); +}; + +export const PageList = ({ + pageList, + showFavoriteTag = false, + isTrash = false, +}: { + pageList: PageMeta[]; + showFavoriteTag?: boolean; + isTrash?: boolean; +}) => { + const router = useRouter(); + const { currentWorkspaceId } = useAppState(); + if (pageList.length === 0) { + return ; + } + + return ( + + + + + Title + Created + + {isTrash ? 'Moved to Trash' : 'Updated'} + + + + + + {pageList.map((pageMeta, index) => { + return ( + { + router.push( + `/workspace/${currentWorkspaceId}/${pageMeta.id}` + ); + }} + > + + + + {pageMeta.mode === 'edgeless' ? ( + + ) : ( + + )} + + {pageMeta.title || 'Untitled'} + + + {showFavoriteTag && } + + + + + { + e.stopPropagation(); + }} + > + {isTrash ? ( + + ) : ( + + )} + + + ); + })} + +
+
+ ); +}; + +export default PageList; diff --git a/packages/app/src/components/page-list/operation-cell.tsx b/packages/app/src/components/page-list/operation-cell.tsx new file mode 100644 index 0000000000..7416b65d9a --- /dev/null +++ b/packages/app/src/components/page-list/operation-cell.tsx @@ -0,0 +1,109 @@ +import { useConfirm } from '@/providers/confirm-provider'; +import { PageMeta } from '@/providers/app-state-provider'; +import { Menu, MenuItem } from '@/ui/menu'; +import { Wrapper } from '@/ui/layout'; +import { IconButton } from '@/ui/button'; +import { + MoreVerticalIcon, + RestoreIcon, + DeleteIcon, + FavouritesIcon, + FavouritedIcon, + OpenInNewIcon, + TrashIcon, +} from '@blocksuite/icons'; +import { toast } from '@/ui/toast'; +import { usePageHelper } from '@/hooks/use-page-helper'; + +export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { + const { id, favorite } = pageMeta; + const { openPage } = usePageHelper(); + const { toggleFavoritePage, toggleDeletePage } = usePageHelper(); + const { confirm } = useConfirm(); + + const OperationMenu = ( + <> + { + toggleFavoritePage(id); + toast(!favorite ? 'Removed to Favourites' : 'Added to Favourites'); + }} + icon={favorite ? : } + > + {favorite ? 'Remove' : 'Add'} to favourites + + { + openPage(id, {}, true); + }} + icon={} + > + Open in new tab + + { + confirm({ + title: 'Delete page?', + content: `${pageMeta.title || 'Untitled'} will be moved to Trash`, + confirmText: 'Delete', + confirmType: 'danger', + }).then(confirm => { + confirm && toggleDeletePage(id); + toast('Moved to Trash'); + }); + }} + icon={} + > + Delete + + + ); + return ( + + + + + + + + ); +}; + +export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { + const { id } = pageMeta; + const { openPage, getPageMeta } = usePageHelper(); + const { toggleDeletePage, permanentlyDeletePage } = usePageHelper(); + const { confirm } = useConfirm(); + + return ( + + { + toggleDeletePage(id); + toast(`${getPageMeta(id)?.title || 'Untitled'} restored`); + openPage(id); + }} + > + + + { + confirm({ + title: 'Delete permanently?', + content: "Once deleted, you can't undo this action.", + confirmText: 'Delete', + confirmType: 'danger', + }).then(confirm => { + confirm && permanentlyDeletePage(id); + toast('Permanently deleted'); + }); + }} + > + + + + ); +}; diff --git a/packages/app/src/components/page-list/styles.ts b/packages/app/src/components/page-list/styles.ts new file mode 100644 index 0000000000..fcf80e813f --- /dev/null +++ b/packages/app/src/components/page-list/styles.ts @@ -0,0 +1,51 @@ +import { displayFlex, styled } from '@/styles'; +import { TableRow } from '@/ui/table'; + +export const StyledTableContainer = styled.div(() => { + return { + height: 'calc(100vh - 60px)', + padding: '78px 72px', + overflowY: 'auto', + }; +}); +export const StyledTitleWrapper = styled.div(({ theme }) => { + return { + ...displayFlex('flex-start', 'center'), + a: { + color: 'inherit', + }, + 'a:visited': { + color: 'unset', + }, + 'a:hover': { + color: theme.colors.primaryColor, + }, + }; +}); +export const StyledTitleLink = styled.div(({ theme }) => { + return { + maxWidth: '80%', + marginRight: '18px', + ...displayFlex('flex-start', 'center'), + color: theme.colors.textColor, + '>svg': { + fontSize: '24px', + marginRight: '12px', + color: theme.colors.iconColor, + }, + }; +}); + +export const StyledTableRow = styled(TableRow)(() => { + return { + cursor: 'pointer', + '.favorite-button': { + display: 'none', + }, + '&:hover': { + '.favorite-button': { + display: 'flex', + }, + }, + }; +}); diff --git a/packages/app/src/components/provider-composer.ts b/packages/app/src/components/provider-composer.ts new file mode 100644 index 0000000000..23691240ba --- /dev/null +++ b/packages/app/src/components/provider-composer.ts @@ -0,0 +1,18 @@ +import { cloneElement, FC, PropsWithChildren, ReactNode } from 'react'; + +export const ProviderComposer: FC< + PropsWithChildren<{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + contexts: any; + }> +> = ({ contexts, children }) => + contexts.reduceRight( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (kids: ReactNode, parent: any) => + cloneElement(parent, { + children: kids, + }), + children + ); + +export default ProviderComposer; diff --git a/packages/app/src/components/quick-search/config.ts b/packages/app/src/components/quick-search/config.ts new file mode 100644 index 0000000000..7c5b8afe72 --- /dev/null +++ b/packages/app/src/components/quick-search/config.ts @@ -0,0 +1,24 @@ +import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons'; + +export const config = (currentWorkspaceId: string) => { + const List = [ + { + title: 'All pages', + href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '', + icon: AllPagesIcon, + }, + { + title: 'Favourites', + href: currentWorkspaceId + ? `/workspace/${currentWorkspaceId}/favorite` + : '', + icon: FavouritesIcon, + }, + { + title: 'Trash', + href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '', + icon: TrashIcon, + }, + ]; + return List; +}; diff --git a/packages/app/src/components/quick-search/footer.tsx b/packages/app/src/components/quick-search/footer.tsx new file mode 100644 index 0000000000..5f03b6c85e --- /dev/null +++ b/packages/app/src/components/quick-search/footer.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { AddIcon } from '@blocksuite/icons'; +import { StyledModalFooterContent } from './style'; +import { useModal } from '@/providers/global-modal-provider'; +import { Command } from 'cmdk'; +import { usePageHelper } from '@/hooks/use-page-helper'; + +export const Footer = (props: { query: string }) => { + const { triggerQuickSearchModal } = useModal(); + const { openPage, createPage } = usePageHelper(); + const query = props.query; + + return ( + { + const pageId = await createPage({ title: query }); + if (pageId) { + openPage(pageId); + } + + triggerQuickSearchModal(); + }} + > + + + {query ? ( + New "{query}" page + ) : ( + New page + )} + + + ); +}; diff --git a/packages/app/src/components/quick-search/index.tsx b/packages/app/src/components/quick-search/index.tsx new file mode 100644 index 0000000000..7475d97567 --- /dev/null +++ b/packages/app/src/components/quick-search/index.tsx @@ -0,0 +1,118 @@ +import { Modal, ModalWrapper } from '@/ui/modal'; +import { + StyledContent, + StyledModalHeader, + StyledModalFooter, + StyledModalDivider, + StyledShortcut, +} from './style'; +import { Input } from './input'; +import { Results } from './results'; +import { Footer } from './footer'; +import { Command } from 'cmdk'; +import { useEffect, useState } from 'react'; +import { useModal } from '@/providers/global-modal-provider'; +import { getUaHelper } from '@/utils'; +import { useAppState } from '@/providers/app-state-provider'; +type TransitionsModalProps = { + open: boolean; + onClose: () => void; +}; +const isMac = () => { + return getUaHelper().isMacOs; +}; +export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { + const [query, setQuery] = useState(''); + const [loading, setLoading] = useState(true); + const [showCreatePage, setShowCreatePage] = useState(true); + const { triggerQuickSearchModal } = useModal(); + const { currentWorkspaceId, workspacesMeta } = useAppState(); + + const currentWorkspace = workspacesMeta.find( + meta => String(meta.id) === String(currentWorkspaceId) + ); + const isPublic = currentWorkspace?.public; + + // Add ‘⌘+K’ shortcut keys as switches + useEffect(() => { + const down = (e: KeyboardEvent) => { + if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) { + const selection = window.getSelection(); + if (selection?.toString()) { + triggerQuickSearchModal(false); + return; + } + if (selection?.isCollapsed) { + triggerQuickSearchModal(!open); + } + } + }; + if (!open) { + setQuery(''); + } + document.addEventListener('keydown', down, { capture: true }); + return () => + document.removeEventListener('keydown', down, { capture: true }); + }, [open, triggerQuickSearchModal]); + + return ( + + + { + if ( + e.key === 'ArrowDown' || + e.key === 'ArrowUp' || + e.key === 'ArrowLeft' || + e.key === 'ArrowRight' + ) { + e.stopPropagation(); + } + }} + > + + + {isMac() ? '⌘ + K' : 'Ctrl + K'} + + + + + + + {isPublic ? ( + <> + ) : showCreatePage ? ( + <> + + +
+ + + ) : null} + + + + + ); +}; + +export default QuickSearch; diff --git a/packages/app/src/components/quick-search/input.tsx b/packages/app/src/components/quick-search/input.tsx new file mode 100644 index 0000000000..a3897675fb --- /dev/null +++ b/packages/app/src/components/quick-search/input.tsx @@ -0,0 +1,90 @@ +import React, { + Dispatch, + SetStateAction, + useEffect, + useRef, + useState, +} from 'react'; +import { SearchIcon } from '@blocksuite/icons'; +import { StyledInputContent, StyledLabel } from './style'; +import { Command } from 'cmdk'; +import { useAppState } from '@/providers/app-state-provider'; +export const Input = (props: { + query: string; + setQuery: Dispatch>; + setLoading: Dispatch>; +}) => { + const [isComposition, setIsComposition] = useState(false); + const [inputValue, setInputValue] = useState(''); + const inputRef = useRef(null); + const { currentWorkspaceId, workspacesMeta, currentWorkspace } = + useAppState(); + const isPublic = workspacesMeta.find( + meta => String(meta.id) === String(currentWorkspaceId) + )?.public; + useEffect(() => { + inputRef.current?.addEventListener( + 'blur', + () => { + inputRef.current?.focus(); + }, + true + ); + return inputRef.current?.focus(); + }, [inputRef]); + useEffect(() => { + return setInputValue(props.query); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + return ( + + + + + { + setIsComposition(true); + }} + onCompositionEnd={e => { + props.setQuery(e.data); + setIsComposition(false); + if (!props.query) { + props.setLoading(true); + } + }} + onValueChange={str => { + setInputValue(str); + if (!isComposition) { + props.setQuery(str); + if (!props.query) { + props.setLoading(true); + } + } + }} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === 'a' && e.metaKey) { + e.stopPropagation(); + inputRef.current?.select(); + return; + } + if (isComposition) { + if ( + e.key === 'ArrowDown' || + e.key === 'ArrowUp' || + e.key === 'Enter' + ) { + e.stopPropagation(); + } + } + }} + placeholder={ + isPublic + ? `Search in ${currentWorkspace?.meta.name}` + : 'Quick Search...' + } + /> + + ); +}; diff --git a/packages/app/src/components/quick-search/noResultSVG.tsx b/packages/app/src/components/quick-search/noResultSVG.tsx new file mode 100644 index 0000000000..16aabb73d9 --- /dev/null +++ b/packages/app/src/components/quick-search/noResultSVG.tsx @@ -0,0 +1,95 @@ +export const NoResultSVG = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/app/src/components/quick-search/results.tsx b/packages/app/src/components/quick-search/results.tsx new file mode 100644 index 0000000000..2a99e389ff --- /dev/null +++ b/packages/app/src/components/quick-search/results.tsx @@ -0,0 +1,102 @@ +import { Command } from 'cmdk'; +import { StyledListItem, StyledNotFound } from './style'; +import { useModal } from '@/providers/global-modal-provider'; +import { PaperIcon, EdgelessIcon } from '@blocksuite/icons'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { useAppState } from '@/providers/app-state-provider'; +import { useRouter } from 'next/router'; +import { config } from './config'; +import { NoResultSVG } from './noResultSVG'; +import usePageHelper from '@/hooks/use-page-helper'; +import usePageMetaList from '@/hooks/use-page-meta-list'; +export const Results = (props: { + query: string; + loading: boolean; + setLoading: Dispatch>; + setShowCreatePage: Dispatch>; +}) => { + const query = props.query; + const loading = props.loading; + const setLoading = props.setLoading; + const setShowCreatePage = props.setShowCreatePage; + const { triggerQuickSearchModal } = useModal(); + const pageMetaList = usePageMetaList(); + const { openPage } = usePageHelper(); + const router = useRouter(); + const { currentWorkspaceId } = useAppState(); + const { search } = usePageHelper(); + const List = config(currentWorkspaceId); + const [results, setResults] = useState(new Map()); + useEffect(() => { + setResults(search(query)); + setLoading(false); + //Save the Map obtained from the search as state + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [query, setResults, setLoading]); + const pageIds = [...results.values()]; + + const resultsPageMeta = pageMetaList.filter( + page => pageIds.indexOf(page.id) > -1 && !page.trash + ); + + useEffect(() => { + setShowCreatePage(resultsPageMeta.length ? false : true); + //Determine whether to display the ‘+ New page’ + }, [resultsPageMeta, setShowCreatePage]); + return loading ? null : ( + <> + {query ? ( + resultsPageMeta.length ? ( + + {resultsPageMeta.map(result => { + return ( + { + openPage(result.id); + triggerQuickSearchModal(); + }} + value={result.id} + > + + {result.mode === 'edgeless' ? ( + + ) : ( + + )} + {result.title} + + + ); + })} + + ) : ( + + Find 0 result + + + ) + ) : ( + + {List.map(link => { + return ( + { + router.push(link.href); + triggerQuickSearchModal(); + }} + > + + + {link.title} + + + ); + })} + + )} + + ); +}; diff --git a/packages/app/src/components/quick-search/style.ts b/packages/app/src/components/quick-search/style.ts new file mode 100644 index 0000000000..c37e85e231 --- /dev/null +++ b/packages/app/src/components/quick-search/style.ts @@ -0,0 +1,153 @@ +import { displayFlex, styled } from '@/styles'; + +export const StyledContent = styled('div')(({ theme }) => { + return { + minHeight: '220px', + maxHeight: '55vh', + width: '100%', + overflow: 'auto', + marginBottom: '10px', + ...displayFlex('center', 'flex-start'), + color: theme.colors.popoverColor, + letterSpacing: '0.06em', + '[cmdk-group-heading]': { + margin: '5px 16px', + fontSize: theme.font.sm, + fontWeight: '500', + }, + '[aria-selected="true"]': { + transition: 'background .15s, color .15s', + borderRadius: '5px', + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; +}); +export const StyledJumpTo = styled('div')(({ theme }) => { + return { + ...displayFlex('center', 'start'), + flexDirection: 'column', + padding: '10px 10px 10px 0', + fontSize: theme.font.sm, + strong: { + fontWeight: '500', + marginBottom: '10px', + }, + }; +}); +export const StyledNotFound = styled('div')(({ theme }) => { + return { + width: '612px', + ...displayFlex('center', 'center'), + flexDirection: 'column', + padding: '5px 16px', + fontSize: theme.font.sm, + span: { + width: '100%', + fontWeight: '500', + }, + + '>svg': { + marginTop: '10px', + fontSize: '150px', + }, + }; +}); +export const StyledInputContent = styled('div')(({ theme }) => { + return { + margin: '13px 0', + ...displayFlex('space-between', 'center'), + input: { + width: '492px', + height: '22px', + padding: '0 12px', + fontSize: theme.font.base, + ...displayFlex('space-between', 'center'), + letterSpacing: '0.06em', + color: theme.colors.popoverColor, + + '::placeholder': { + color: theme.colors.placeHolderColor, + }, + }, + }; +}); +export const StyledShortcut = styled('div')(({ theme }) => { + return { color: theme.colors.placeHolderColor, fontSize: theme.font.sm }; +}); + +export const StyledLabel = styled('label')(({ theme }) => { + return { + width: '24px', + height: '24px', + color: theme.colors.iconColor, + }; +}); + +export const StyledModalHeader = styled('div')(({ theme }) => { + return { + height: '48px', + margin: '12px 24px 0px 24px', + ...displayFlex('space-between', 'center'), + color: theme.colors.popoverColor, + }; +}); +export const StyledModalDivider = styled('div')(({ theme }) => { + return { + width: 'auto', + height: '0', + margin: '6px 16px 6.5px 16px', + position: 'relative', + borderTop: `0.5px solid ${theme.colors.placeHolderColor}`, + }; +}); + +export const StyledModalFooter = styled('div')(({ theme }) => { + return { + fontSize: theme.font.sm, + lineHeight: '22px', + marginBottom: '8px', + textAlign: 'center', + ...displayFlex('center', 'center'), + color: theme.colors.popoverColor, + '[aria-selected="true"]': { + transition: 'background .15s, color .15s', + borderRadius: '5px', + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; +}); +export const StyledModalFooterContent = styled.button(({ theme }) => { + return { + width: '612px', + height: '32px', + fontSize: theme.font.sm, + lineHeight: '22px', + textAlign: 'center', + ...displayFlex('center', 'center'), + color: 'inherit', + borderRadius: '5px', + transition: 'background .15s, color .15s', + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + }; +}); +export const StyledListItem = styled.button(({ theme }) => { + return { + width: '612px', + height: '32px', + fontSize: theme.font.sm, + color: 'inherit', + paddingLeft: '12px', + borderRadius: '5px', + transition: 'background .15s, color .15s', + ...displayFlex('flex-start', 'center'), + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + }; +}); diff --git a/packages/app/src/components/shortcuts-modal/config.ts b/packages/app/src/components/shortcuts-modal/config.ts index 5638268a99..4114756a6d 100644 --- a/packages/app/src/components/shortcuts-modal/config.ts +++ b/packages/app/src/components/shortcuts-modal/config.ts @@ -6,6 +6,7 @@ export const macKeyboardShortcuts = { Underline: '⌘+U', Strikethrough: '⌘+⇧+S', 'Inline code': ' ⌘+E', + 'Code block': '⌘+⌥+C', Link: '⌘+K', 'Body text': '⌘+⌥+0', 'Heading 1': '⌘+⌥+1', @@ -23,7 +24,9 @@ export const macMarkdownShortcuts = { Italic: '*Text* ', Underline: '~Text~ ', Strikethrough: '~~Text~~ ', + Divider: '***', 'Inline code': '`Text` ', + 'Code block': '``` Space', 'Heading 1': '# Text', 'Heading 2': '## Text', 'Heading 3': '### Text', @@ -40,6 +43,7 @@ export const windowsKeyboardShortcuts = { Underline: 'Ctrl+U', Strikethrough: 'Ctrl+Shift+S', 'Inline code': ' Ctrl+E', + 'Code block': 'Ctrl+Alt+C', Link: 'Ctrl+K', 'Body text': 'Ctrl+Shift+0', 'Heading 1': 'Ctrl+Shift+1', @@ -57,6 +61,7 @@ export const winMarkdownShortcuts = { Underline: '~Text~ ', Strikethrough: '~~Text~~ ', 'Inline code': '`Text` ', + 'Code block': '``` Text', 'Heading 1': '# Text', 'Heading 2': '## Text', 'Heading 3': '### Text', diff --git a/packages/app/src/components/shortcuts-modal/index.tsx b/packages/app/src/components/shortcuts-modal/index.tsx index 752746ef28..63a4246d64 100644 --- a/packages/app/src/components/shortcuts-modal/index.tsx +++ b/packages/app/src/components/shortcuts-modal/index.tsx @@ -6,7 +6,6 @@ import { StyledShortcutsModal, StyledSubTitle, StyledTitle, - CloseButton, } from './style'; import { macKeyboardShortcuts, @@ -14,15 +13,16 @@ import { windowsKeyboardShortcuts, winMarkdownShortcuts, } from '@/components/shortcuts-modal/config'; -import CloseIcon from '@mui/icons-material/Close'; import Slide from '@mui/material/Slide'; +import { ModalCloseButton } from '@/ui/modal'; +import { getUaHelper } from '@/utils'; type ModalProps = { open: boolean; onClose: () => void; }; const isMac = () => { - return /macintosh|mac os x/i.test(navigator.userAgent); + return getUaHelper().isMacOs; }; export const ShortcutsModal = ({ open, onClose }: ModalProps) => { @@ -42,13 +42,15 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => { Shortcuts - { onClose(); }} - > - - + /> Keyboard Shortcuts diff --git a/packages/app/src/components/shortcuts-modal/style.ts b/packages/app/src/components/shortcuts-modal/style.ts index a38e9da25d..fabe9b3fee 100644 --- a/packages/app/src/components/shortcuts-modal/style.ts +++ b/packages/app/src/components/shortcuts-modal/style.ts @@ -38,7 +38,7 @@ export const StyledSubTitle = styled.div(({ theme }) => ({ marginTop: '28px', padding: '0 16px', })); -export const StyledModalHeader = styled.div(({ theme }) => ({ +export const StyledModalHeader = styled.div(() => ({ ...displayFlex('space-between', 'center'), paddingTop: '8px 4px 0 4px', width: '100%', @@ -57,22 +57,3 @@ export const StyledListItem = styled.div(({ theme }) => ({ fontSize: theme.font.sm, padding: '0 16px', })); - -export const CloseButton = styled('div')(({ theme }) => { - return { - width: '24px', - height: '24px', - borderRadius: '5px', - color: theme.colors.iconColor, - cursor: 'pointer', - ...displayFlex('center', 'center'), - svg: { - width: '15px', - height: '15px', - }, - ':hover': { - background: theme.colors.hoverBackground, - color: theme.colors.primaryColor, - }, - }; -}); diff --git a/packages/app/src/components/simple-counter/index.ts b/packages/app/src/components/simple-counter/index.ts index 8b7f8cbe96..3f63324fa2 100644 --- a/packages/app/src/components/simple-counter/index.ts +++ b/packages/app/src/components/simple-counter/index.ts @@ -6,6 +6,7 @@ export const tagName = 'simple-counter'; // Adapt React in order to be able to use custom tags properly declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace JSX { interface IntrinsicElements { [tagName]: PersonInfoProps; @@ -49,10 +50,10 @@ export class Counter extends LitElement {
`; } - private _increment(e: Event) { + private _increment() { this.count++; } - private _subtract(e: Event) { + private _subtract() { this.count--; } } diff --git a/packages/app/src/components/workspace-layout/index.tsx b/packages/app/src/components/workspace-layout/index.tsx new file mode 100644 index 0000000000..b77d86fada --- /dev/null +++ b/packages/app/src/components/workspace-layout/index.tsx @@ -0,0 +1,35 @@ +import HelpIsland from '@/components/help-island'; +import { WorkSpaceSliderBar } from '@/components/workspace-slider-bar'; +import { useRouter } from 'next/router'; +import { StyledPage, StyledWrapper } from './styles'; +import { PropsWithChildren } from 'react'; +import useEnsureWorkspace from '@/hooks/use-ensure-workspace'; +import { PageLoading } from '@/components/loading'; + +export const WorkspaceDefender = ({ children }: PropsWithChildren) => { + const { workspaceLoaded } = useEnsureWorkspace(); + return <>{workspaceLoaded ? children : }; +}; + +export const WorkspaceLayout = ({ children }: PropsWithChildren) => { + const router = useRouter(); + + return ( + + + + {children} + + + + ); +}; + +export const Layout = ({ children }: PropsWithChildren) => { + return ( + + {children} + + ); +}; +export default Layout; diff --git a/packages/app/src/components/workspace-layout/styles.ts b/packages/app/src/components/workspace-layout/styles.ts new file mode 100644 index 0000000000..e935901b3e --- /dev/null +++ b/packages/app/src/components/workspace-layout/styles.ts @@ -0,0 +1,18 @@ +import { styled } from '@/styles'; + +export const StyledPage = styled('div')(({ theme }) => { + return { + height: '100vh', + backgroundColor: theme.colors.pageBackground, + transition: 'background-color .5s', + display: 'flex', + flexGrow: '1', + }; +}); + +export const StyledWrapper = styled('div')(() => { + return { + flexGrow: 1, + position: 'relative', + }; +}); diff --git a/packages/app/src/components/workspace-setting/general/delete/delete.tsx b/packages/app/src/components/workspace-setting/general/delete/delete.tsx new file mode 100644 index 0000000000..2d0663d734 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/delete/delete.tsx @@ -0,0 +1,84 @@ +import Modal from '@/ui/modal'; +import Input from '@/ui/input'; +import { + StyledModalHeader, + StyledTextContent, + StyledModalWrapper, + StyledInputContent, + StyledButtonContent, + StyledWorkspaceName, +} from './style'; +import { useState } from 'react'; +import { ModalCloseButton } from '@/ui/modal'; +import { Button } from '@/ui/button'; +import { deleteWorkspace } from '@affine/data-services'; +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider'; + +interface WorkspaceDeleteProps { + open: boolean; + onClose: () => void; + workspaceName: string; + workspaceId: string; + nextWorkSpaceId: string; +} + +export const WorkspaceDelete = ({ + open, + onClose, + workspaceId, + workspaceName, + nextWorkSpaceId, +}: WorkspaceDeleteProps) => { + const [deleteStr, setDeleteStr] = useState(''); + const { refreshWorkspacesMeta } = useAppState(); + const router = useRouter(); + + const handlerInputChange = (workspaceName: string) => { + setDeleteStr(workspaceName); + }; + + const handleDelete = async () => { + await deleteWorkspace({ id: workspaceId }); + router.push(`/workspace/${nextWorkSpaceId}`); + refreshWorkspacesMeta(); + onClose(); + }; + + return ( + + + + Delete Workspace + + This action cannot be undone. This will permanently delete ( + {workspaceName}) along with + all its content. + + + + + + + + + + + ); +}; + +export default WorkspaceDelete; diff --git a/packages/app/src/components/workspace-setting/general/delete/index.ts b/packages/app/src/components/workspace-setting/general/delete/index.ts new file mode 100644 index 0000000000..2a41f028d2 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/delete/index.ts @@ -0,0 +1 @@ +export * from './delete'; diff --git a/packages/app/src/components/workspace-setting/general/delete/style.ts b/packages/app/src/components/workspace-setting/general/delete/style.ts new file mode 100644 index 0000000000..0e3c607e40 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/delete/style.ts @@ -0,0 +1,75 @@ +import { styled } from '@/styles'; + +export const StyledModalWrapper = styled('div')(({ theme }) => { + return { + position: 'relative', + padding: '0px', + width: '460px', + background: theme.colors.popoverBackground, + borderRadius: '12px', + }; +}); + +export const StyledModalHeader = styled('div')(({ theme }) => { + return { + margin: '44px 0px 12px 0px', + width: '460px', + fontWeight: '600', + fontSize: '20px;', + textAlign: 'center', + color: theme.colors.popoverColor, + }; +}); + +// export const StyledModalContent = styled('div')(({ theme }) => {}); + +export const StyledTextContent = styled('div')(() => { + return { + margin: 'auto', + width: '425px', + fontFamily: 'Avenir Next', + fontStyle: 'normal', + fontWeight: '400', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'center', + }; +}); + +export const StyledInputContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '40px 0 24px 0', + }; +}); + +export const StyledButtonContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '0px 0 32px 0', + }; +}); + +export const StyledWorkspaceName = styled('span')(() => { + return { + color: '#E8178A', + }; +}); + +// export const StyledCancelButton = styled(Button)(({ theme }) => { +// return { +// width: '100px', +// justifyContent: 'center', +// }; +// }); + +// export const StyledDeleteButton = styled(Button)(({ theme }) => { +// return { +// width: '100px', +// justifyContent: 'center', +// }; +// }); diff --git a/packages/app/src/components/workspace-setting/general/general.tsx b/packages/app/src/components/workspace-setting/general/general.tsx new file mode 100644 index 0000000000..74f355fd12 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/general.tsx @@ -0,0 +1,166 @@ +import { + StyledDeleteButtonContainer, + StyledSettingAvatar, + StyledSettingAvatarContent, + StyledSettingInputContainer, +} from './style'; +import { StyledSettingH2 } from '../style'; + +import { useState } from 'react'; +import { Button } from '@/ui/button'; +import Input from '@/ui/input'; +import { uploadBlob, Workspace, WorkspaceType } from '@affine/data-services'; +import { useAppState } from '@/providers/app-state-provider'; +import { WorkspaceDetails } from '@/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent'; +import { WorkspaceDelete } from './delete'; +import { Workspace as StoreWorkspace } from '@blocksuite/store'; +import { debounce } from '@/utils'; +import { WorkspaceLeave } from './leave'; +import { Upload } from '@/components/file-upload'; + +export const GeneralPage = ({ + workspace, + owner, +}: { + workspace: Workspace; + owner: WorkspaceDetails[string]['owner']; + workspaces: Record; +}) => { + const { + user, + currentWorkspace, + workspacesMeta, + workspaces, + refreshWorkspacesMeta, + } = useAppState(); + const [showDelete, setShowDelete] = useState(false); + const [showLeave, setShowLeave] = useState(false); + const [uploading, setUploading] = useState(false); + const [workspaceName, setWorkspaceName] = useState( + workspaces[workspace.id]?.meta.name || + (workspace.type === WorkspaceType.Private && user ? user.name : '') + ); + const debouncedRefreshWorkspacesMeta = debounce(() => { + refreshWorkspacesMeta(); + }, 100); + const isOwner = user && owner.id === user.id; + const handleChangeWorkSpaceName = (newName: string) => { + setWorkspaceName(newName); + currentWorkspace?.meta.setName(newName); + workspaces[workspace.id]?.meta.setName(newName); + debouncedRefreshWorkspacesMeta(); + }; + const currentWorkspaceIndex = workspacesMeta.findIndex( + meta => meta.id === workspace.id + ); + const nextWorkSpaceId = + currentWorkspaceIndex === workspacesMeta.length - 1 + ? workspacesMeta[currentWorkspaceIndex - 1]?.id + : workspacesMeta[currentWorkspaceIndex + 1]?.id; + const handleClickDelete = () => { + setShowDelete(true); + }; + const handleCloseDelete = () => { + setShowDelete(false); + }; + + const handleClickLeave = () => { + setShowLeave(true); + }; + const handleCloseLeave = () => { + setShowLeave(false); + }; + + const fileChange = async (file: File) => { + setUploading(true); + const blob = new Blob([file], { type: file.type }); + const blobId = await uploadBlob({ blob }).finally(() => { + setUploading(false); + }); + if (blobId) { + currentWorkspace?.meta.setAvatar(blobId); + workspaces[workspace.id]?.meta.setAvatar(blobId); + setUploading(false); + debouncedRefreshWorkspacesMeta(); + } + }; + + return workspace ? ( +
+ Workspace Avatar + + + {workspaces[workspace.id]?.meta.name[0]} + + + + + {/* TODO: add upload logic */} + {/* {isOwner ? ( + upload + ) : null} */} + {/* */} + + Workspace Name + + + + Workspace Owner + + + + + {isOwner ? ( + <> + + + + ) : ( + <> + + + + )} + +
+ ) : null; +}; diff --git a/packages/app/src/components/workspace-setting/general/index.ts b/packages/app/src/components/workspace-setting/general/index.ts new file mode 100644 index 0000000000..12bc2fe54d --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/index.ts @@ -0,0 +1 @@ +export * from './general'; diff --git a/packages/app/src/components/workspace-setting/general/leave/index.ts b/packages/app/src/components/workspace-setting/general/leave/index.ts new file mode 100644 index 0000000000..ae98410fad --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/leave/index.ts @@ -0,0 +1 @@ +export * from './leave'; diff --git a/packages/app/src/components/workspace-setting/general/leave/leave.tsx b/packages/app/src/components/workspace-setting/general/leave/leave.tsx new file mode 100644 index 0000000000..41fae82d07 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/leave/leave.tsx @@ -0,0 +1,64 @@ +import Modal from '@/ui/modal'; +import { + StyledModalHeader, + StyledTextContent, + StyledModalWrapper, + StyledButtonContent, +} from './style'; +import { ModalCloseButton } from '@/ui/modal'; +import { Button } from '@/ui/button'; +import { leaveWorkspace } from '@affine/data-services'; +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider'; + +interface WorkspaceDeleteProps { + open: boolean; + onClose: () => void; + workspaceName: string; + workspaceId: string; + nextWorkSpaceId: string; +} + +export const WorkspaceLeave = ({ + open, + onClose, + nextWorkSpaceId, + workspaceId, +}: WorkspaceDeleteProps) => { + const router = useRouter(); + const { refreshWorkspacesMeta } = useAppState(); + const handleLeave = async () => { + await leaveWorkspace({ id: workspaceId }); + router.push(`/workspace/${nextWorkSpaceId}`); + refreshWorkspacesMeta(); + onClose(); + }; + + return ( + + + + Leave Workspace + + After you leave, you will not be able to access all the contents of + this workspace. + + + + + + + + ); +}; + +export default WorkspaceLeave; diff --git a/packages/app/src/components/workspace-setting/general/leave/style.ts b/packages/app/src/components/workspace-setting/general/leave/style.ts new file mode 100644 index 0000000000..1d9696af37 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/leave/style.ts @@ -0,0 +1,46 @@ +import { styled } from '@/styles'; + +export const StyledModalWrapper = styled('div')(({ theme }) => { + return { + position: 'relative', + padding: '0px', + width: '460px', + background: theme.colors.popoverBackground, + borderRadius: '12px', + }; +}); + +export const StyledModalHeader = styled('div')(({ theme }) => { + return { + margin: '44px 0px 12px 0px', + width: '460px', + fontWeight: '600', + fontSize: '20px;', + textAlign: 'center', + color: theme.colors.popoverColor, + }; +}); + +// export const StyledModalContent = styled('div')(({ theme }) => {}); + +export const StyledTextContent = styled('div')(() => { + return { + margin: 'auto', + width: '425px', + fontFamily: 'Avenir Next', + fontStyle: 'normal', + fontWeight: '400', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'center', + }; +}); + +export const StyledButtonContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '0px 0 32px 0', + }; +}); diff --git a/packages/app/src/components/workspace-setting/general/style.ts b/packages/app/src/components/workspace-setting/general/style.ts new file mode 100644 index 0000000000..e4b505f228 --- /dev/null +++ b/packages/app/src/components/workspace-setting/general/style.ts @@ -0,0 +1,28 @@ +import { styled } from '@/styles'; +import MuiAvatar from '@mui/material/Avatar'; + +export const StyledSettingInputContainer = styled('div')(() => { + return { + marginTop: '12px', + }; +}); + +export const StyledDeleteButtonContainer = styled('div')(() => { + return { + marginTop: '154px', + }; +}); + +export const StyledSettingAvatarContent = styled('div')(() => { + return { + marginTop: '12px', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + height: '72px', + }; +}); + +export const StyledSettingAvatar = styled(MuiAvatar)(() => { + return { height: '72px', width: '72px', marginRight: '24px' }; +}); diff --git a/packages/app/src/components/workspace-setting/index.ts b/packages/app/src/components/workspace-setting/index.ts new file mode 100644 index 0000000000..eda9c6aebd --- /dev/null +++ b/packages/app/src/components/workspace-setting/index.ts @@ -0,0 +1 @@ +export * from './workspace-setting'; diff --git a/packages/app/src/components/workspace-setting/style.ts b/packages/app/src/components/workspace-setting/style.ts new file mode 100644 index 0000000000..f59125a9fd --- /dev/null +++ b/packages/app/src/components/workspace-setting/style.ts @@ -0,0 +1,230 @@ +import { styled } from '@/styles'; +import { Button } from '@/ui/button'; +import MuiAvatar from '@mui/material/Avatar'; + +export const StyledSettingContainer = styled('div')(({ theme }) => { + return { + position: 'relative', + display: 'flex', + padding: '0px', + width: '961px', + background: theme.colors.popoverBackground, + borderRadius: '12px', + overflow: 'hidden', + }; +}); + +export const StyledSettingSidebar = styled('div')(({ theme }) => { + { + return { + width: '212px', + height: '620px', + background: theme.mode === 'dark' ? '#272727' : '#FBFBFC', + flexShrink: 0, + flexGrow: 0, + }; + } +}); + +export const StyledSettingContent = styled('div')(() => { + return { + paddingLeft: '48px', + height: '620px', + }; +}); + +export const StyledSetting = styled('div')(({ theme }) => { + { + return { + width: '236px', + height: '620px', + background: theme.mode === 'dark' ? '#272727' : '#FBFBFC', + }; + } +}); + +export const StyledSettingSidebarHeader = styled('div')(() => { + { + return { + fontWeight: '500', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'center', + marginTop: '37px', + }; + } +}); + +export const StyledSettingTabContainer = styled('ul')(() => { + { + return { + display: 'flex', + flexDirection: 'column', + marginTop: '25px', + }; + } +}); + +export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>( + ({ theme, isActive }) => { + { + return { + display: 'flex', + marginBottom: '12px', + padding: '0 24px', + height: '32px', + color: isActive ? theme.colors.primaryColor : theme.colors.textColor, + fontWeight: '400', + fontSize: '16px', + lineHeight: '32px', + cursor: 'pointer', + }; + } + } +); + +export const StyledSettingTagIconContainer = styled('div')(() => { + return { + display: 'flex', + alignItems: 'center', + marginRight: '14.64px', + width: '14.47px', + fontSize: '14.47px', + }; +}); + +export const StyledSettingH2 = styled('h2')<{ marginTop?: number }>( + ({ marginTop }) => { + return { + fontWeight: '500', + fontSize: '18px', + lineHeight: '26px', + marginTop: marginTop ? `${marginTop}px` : '0px', + }; + } +); + +export const StyledAvatarUploadBtn = styled(Button)(({ theme }) => { + return { + backgroundColor: theme.colors.hoverBackground, + color: theme.colors.primaryColor, + margin: '0 12px 0 24px', + }; +}); + +export const StyledMemberTitleContainer = styled('div')(() => { + return { + display: 'flex', + marginTop: '60px', + fontWeight: '500', + }; +}); + +export const StyledMemberAvatar = styled(MuiAvatar)(() => { + return { height: '40px', width: '40px' }; +}); + +export const StyledMemberNameContainer = styled('div')(() => { + return { + display: 'flex', + alignItems: 'center', + width: '402px', + }; +}); + +export const StyledMemberRoleContainer = styled('div')(() => { + return { + display: 'flex', + alignItems: 'center', + width: '222px', + }; +}); + +export const StyledMemberListContainer = styled('ul')(() => { + return { + marginTop: '15px', + height: '432px', + overflowY: 'scroll', + }; +}); + +export const StyledMemberListItem = styled('li')(() => { + return { + display: 'flex', + alignItems: 'center', + height: '72px', + }; +}); + +export const StyledMemberInfo = styled('div')(() => { + return { + paddingLeft: '12px', + }; +}); + +export const StyledMemberName = styled('div')(({ theme }) => { + return { + fontWeight: '400', + fontSize: '18px', + lineHeight: '16px', + color: theme.colors.textColor, + }; +}); + +export const StyledMemberEmail = styled('div')(({ theme }) => { + return { + fontWeight: '400', + fontSize: '16px', + lineHeight: '22px', + color: theme.colors.iconColor, + }; +}); + +export const StyledMemberButtonContainer = styled('div')(() => { + return { + marginTop: '14px', + }; +}); + +export const StyledMoreVerticalButton = styled('button')(() => { + return { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '24px', + height: '24px', + cursor: 'pointer', + }; +}); + +export const StyledPublishExplanation = styled('div')(() => { + return { + marginTop: '56px', + paddingRight: '48px', + fontWeight: '500', + fontSize: '18px', + lineHeight: '26px', + }; +}); + +export const StyledPublishCopyContainer = styled('div')(() => { + return { + marginTop: '12px', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + height: '38px', + }; +}); + +export const StyledCopyButtonContainer = styled('div')(() => { + return { + marginLeft: '12px', + }; +}); + +export const StyledPublishContent = styled('div')(() => { + return { + height: '494px', + }; +}); diff --git a/packages/app/src/components/workspace-setting/workspace-setting.tsx b/packages/app/src/components/workspace-setting/workspace-setting.tsx new file mode 100644 index 0000000000..ee67205ed9 --- /dev/null +++ b/packages/app/src/components/workspace-setting/workspace-setting.tsx @@ -0,0 +1,361 @@ +import Modal, { ModalCloseButton } from '@/ui/modal'; +import { + StyledCopyButtonContainer, + StyledMemberAvatar, + StyledMemberButtonContainer, + StyledMemberEmail, + StyledMemberInfo, + StyledMemberListContainer, + StyledMemberListItem, + StyledMemberName, + StyledMemberNameContainer, + StyledMemberRoleContainer, + StyledMemberTitleContainer, + StyledMoreVerticalButton, + StyledPublishContent, + StyledPublishCopyContainer, + StyledPublishExplanation, + StyledSettingContainer, + StyledSettingContent, + StyledSettingH2, + StyledSettingSidebar, + StyledSettingSidebarHeader, + StyledSettingTabContainer, + StyledSettingTagIconContainer, + WorkspaceSettingTagItem, +} from './style'; +import { + EditIcon, + UsersIcon, + PublishIcon, + MoreVerticalIcon, + EmailIcon, + TrashIcon, +} from '@blocksuite/icons'; +import { useCallback, useEffect, useState } from 'react'; +import { Button, IconButton } from '@/ui/button'; +import Input from '@/ui/input'; +import { InviteMembers } from '../invite-members/index'; +import { + getWorkspaceMembers, + Workspace, + Member, + removeMember, + updateWorkspace, +} from '@affine/data-services'; +import { Avatar } from '@mui/material'; +import { Menu, MenuItem } from '@/ui/menu'; +import { toast } from '@/ui/toast'; +import { Empty } from '@/ui/empty'; +import { useAppState } from '@/providers/app-state-provider'; +import { WorkspaceDetails } from '../workspace-slider-bar/WorkspaceSelector/SelectorPopperContent'; +import { GeneralPage } from './general'; + +enum ActiveTab { + 'general' = 'general', + 'members' = 'members', + 'publish' = 'publish', +} + +type SettingTabProps = { + activeTab: ActiveTab; + onTabChange?: (tab: ActiveTab) => void; +}; + +type WorkspaceSettingProps = { + isShow: boolean; + onClose?: () => void; + workspace: Workspace; + owner: WorkspaceDetails[string]['owner']; +}; + +const WorkspaceSettingTab = ({ activeTab, onTabChange }: SettingTabProps) => { + return ( + + { + onTabChange && onTabChange(ActiveTab.general); + }} + > + + + + General + + { + onTabChange && onTabChange(ActiveTab.members); + }} + > + + + + Members + + { + onTabChange && onTabChange(ActiveTab.publish); + }} + > + + + + Publish + + + ); +}; + +export const WorkspaceSetting = ({ + isShow, + onClose, + workspace, + owner, +}: WorkspaceSettingProps) => { + const { user, workspaces } = useAppState(); + const [activeTab, setActiveTab] = useState(ActiveTab.general); + const handleTabChange = (tab: ActiveTab) => { + setActiveTab(tab); + }; + const handleClickClose = () => { + onClose && onClose(); + }; + const isOwner = user && owner.id === user.id; + useEffect(() => { + // reset tab when modal is closed + if (!isShow) { + setActiveTab(ActiveTab.general); + } + }, [isShow]); + return ( + + + + {isOwner ? ( + + + Workspace Settings + + + + ) : null} + + {activeTab === ActiveTab.general && ( + + )} + {activeTab === ActiveTab.members && workspace && ( + + )} + {activeTab === ActiveTab.publish && ( + + )} + + + + ); +}; + +const MembersPage = ({ workspace }: { workspace: Workspace }) => { + const [isInviteModalShow, setIsInviteModalShow] = useState(false); + const [members, setMembers] = useState([]); + const refreshMembers = useCallback(() => { + getWorkspaceMembers({ + id: workspace.id, + }) + .then(data => { + setMembers(data); + }) + .catch(err => { + console.log(err); + }); + }, [workspace.id]); + useEffect(() => { + refreshMembers(); + }, [refreshMembers]); + + return ( +
+ + + Users({members.length}) + + Access level + + + {members.length === 0 && ( + + )} + {members.length ? ( + members.map(member => { + return ( + + + {member.user.type === 'Registered' ? ( + + ) : ( + + + + )} + + + {member.user.type === 'Registered' ? ( + {member.user.name} + ) : ( + <> + )} + {member.user.email} + + + + {member.accepted + ? member.type !== 99 + ? 'Member' + : 'Workspace Owner' + : 'Pending'} + + + + { + // confirm({ + // title: 'Delete Member?', + // content: `will delete member`, + // confirmText: 'Delete', + // confirmType: 'danger', + // }).then(confirm => { + removeMember({ + permissionId: member.id, + }).then(() => { + // console.log('data: ', data); + toast('Moved to Trash'); + refreshMembers(); + }); + // }); + }} + icon={} + > + Delete + + + } + placement="bottom-end" + disablePortal={true} + > + + + + + + + ); + }) + ) : ( + <> + )} + + + + { + setIsInviteModalShow(false); + }} + onInviteSuccess={() => { + refreshMembers(); + }} + workspaceId={workspace.id} + open={isInviteModalShow} + > + +
+ ); +}; + +const PublishPage = ({ workspace }: { workspace: Workspace }) => { + const shareUrl = window.location.host + '/workspace/' + workspace.id; + const [publicStatus, setPublicStatus] = useState( + workspace.public + ); + const togglePublic = (flag: boolean) => { + updateWorkspace({ + id: workspace.id, + public: flag, + }).then(data => { + setPublicStatus(data?.public); + toast('Updated Public Status Success'); + }); + }; + const copyUrl = () => { + navigator.clipboard.writeText(shareUrl); + toast('Copied url to clipboard'); + }; + return ( +
+ + {publicStatus ? ( + <> + + The current workspace has been published to the web, everyone can + view the contents of this workspace through the link. + + Share with link + + + + + + + + ) : ( + + After publishing to the web, everyone can view the content of this + workspace through the link. + + )} + + {!publicStatus ? ( + + ) : ( + + )} +
+ ); +}; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx new file mode 100644 index 0000000000..161f6dc19b --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/SelectorPopperContent.tsx @@ -0,0 +1,184 @@ +import { InformationIcon, LogOutIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { Divider } from '@/ui/divider'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { SelectorPopperContainer } from './styles'; +import { + PrivateWorkspaceItem, + WorkspaceItem, + CreateWorkspaceItem, + ListItem, + LoginItem, +} from './WorkspaceItem'; +import { WorkspaceSetting } from '@/components/workspace-setting'; +import { useCallback, useEffect, useState } from 'react'; +import { getWorkspaceDetail, WorkspaceType } from '@affine/data-services'; +import { useModal } from '@/providers/global-modal-provider'; + +export type WorkspaceDetails = Record< + string, + { memberCount: number; owner: { id: string; name: string } } +>; + +type SelectorPopperContentProps = { + isShow: boolean; +}; + +export const SelectorPopperContent = ({ + isShow, +}: SelectorPopperContentProps) => { + const { user, workspacesMeta, workspaces, refreshWorkspacesMeta } = + useAppState(); + const [settingWorkspaceId, setSettingWorkspaceId] = useState( + null + ); + const [workSpaceDetails, setWorkSpaceDetails] = useState( + {} + ); + const { triggerContactModal } = useModal(); + + const handleClickSettingWorkspace = (workspaceId: string) => { + setSettingWorkspaceId(workspaceId); + }; + const handleCloseWorkSpace = () => { + setSettingWorkspaceId(null); + }; + const settingWorkspace = settingWorkspaceId + ? workspacesMeta.find(workspace => workspace.id === settingWorkspaceId) + : undefined; + + const refreshDetails = useCallback(async () => { + const workspaceDetailList = await Promise.all( + workspacesMeta.map(async ({ id, type }) => { + if (user) { + if (type === WorkspaceType.Private) { + return { id, member_count: 1, owner: user }; + } else { + const data = await getWorkspaceDetail({ id }); + return { id, ...data } || { id, member_count: 0, owner: user }; + } + } + }) + ); + const workSpaceDetails: WorkspaceDetails = {}; + workspaceDetailList.forEach(details => { + if (details) { + const { id, member_count, owner } = details; + if (!owner) return; + workSpaceDetails[id] = { + memberCount: member_count || 1, + owner: { + id: owner.id, + name: owner.name, + }, + }; + } + }); + setWorkSpaceDetails(workSpaceDetails); + }, [user, workspacesMeta]); + + useEffect(() => { + if (isShow) { + setSettingWorkspaceId(null); + refreshWorkspacesMeta(); + refreshDetails(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isShow]); + + return !user ? ( + + + + } + name="About AFFiNE" + onClick={() => triggerContactModal()} + /> + + ) : ( + + workspace.type === WorkspaceType.Private + )?.id + } + /> + + Workspace + + {workspacesMeta.map(workspace => { + return workspace.type !== WorkspaceType.Private ? ( + + ) : null; + })} + + + {settingWorkspace ? ( + + ) : null} + + } + name="About AFFiNE" + onClick={() => triggerContactModal()} + /> + } + name="Sign out" + onClick={() => { + console.log('Sign out'); + // FIXME: remove token from local storage and reload the page + localStorage.removeItem('affine_token'); + window.location.reload(); + }} + /> + + ); +}; + +const StyledDivider = styled(Divider)({ + margin: '8px 12px', + width: 'calc(100% - 24px)', +}); + +const WorkspaceGroupTitle = styled('div')(({ theme }) => { + return { + color: theme.colors.iconColor, + fontSize: theme.font.sm, + lineHeight: '30px', + height: '30px', + padding: '0 12px', + }; +}); + +const WorkspaceWrapper = styled('div')(() => { + return { + maxHeight: '200px', + overflow: 'auto', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/CreateWorkspaceItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/CreateWorkspaceItem.tsx new file mode 100644 index 0000000000..ccb6526691 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/CreateWorkspaceItem.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; +import { AddIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { + WorkspaceItemAvatar, + WorkspaceItemWrapper, + WorkspaceItemContent, +} from '../styles'; +import { WorkspaceCreate } from './workspace-create'; + +const name = 'Create new Workspace'; + +export const CreateWorkspaceItem = () => { + const [open, setOpen] = useState(false); + return ( + <> + setOpen(true)}> + + + + + {name} + + + setOpen(false)} /> + + ); +}; + +const Name = styled('div')(({ theme }) => { + return { + color: theme.colors.quoteColor, + fontSize: theme.font.base, + fontWeight: 400, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/index.ts new file mode 100644 index 0000000000..8db80b1dc7 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/index.ts @@ -0,0 +1 @@ +export * from './CreateWorkspaceItem'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts new file mode 100644 index 0000000000..38645e20c1 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/index.ts @@ -0,0 +1 @@ +export * from './workspace-create'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/style.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/style.ts new file mode 100644 index 0000000000..4c5162a94f --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/style.ts @@ -0,0 +1,63 @@ +import { styled } from '@/styles'; +import { Button } from '@/ui/button'; + +export const StyledModalWrapper = styled('div')(({ theme }) => { + return { + position: 'relative', + padding: '0px', + width: '460px', + background: theme.colors.popoverBackground, + borderRadius: '12px', + }; +}); + +export const StyledModalHeader = styled('div')(({ theme }) => { + return { + margin: '44px 0px 12px 0px', + width: '460px', + fontWeight: '600', + fontSize: '20px;', + textAlign: 'center', + color: theme.colors.popoverColor, + }; +}); + +// export const StyledModalContent = styled('div')(({ theme }) => {}); + +export const StyledTextContent = styled('div')(() => { + return { + margin: 'auto', + width: '425px', + fontFamily: 'Avenir Next', + fontStyle: 'normal', + fontWeight: '400', + fontSize: '18px', + lineHeight: '26px', + textAlign: 'center', + }; +}); + +export const StyledInputContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '40px 0 24px 0', + }; +}); + +export const StyledButtonContent = styled('div')(() => { + return { + display: 'flex', + flexDirection: 'row', + justifyContent: 'center', + margin: '0px 0 32px 0', + }; +}); + +export const StyledButton = styled(Button)(() => { + return { + width: '260px', + justifyContent: 'center', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx new file mode 100644 index 0000000000..d2cf9f58ef --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/CreateWorkspaceItem/workspace-create/workspace-create.tsx @@ -0,0 +1,118 @@ +import { createWorkspace, uploadBlob } from '@affine/data-services'; +import Modal from '@/ui/modal'; +import Input from '@/ui/input'; +import { + StyledModalHeader, + StyledTextContent, + StyledModalWrapper, + StyledInputContent, + StyledButtonContent, + StyledButton, +} from './style'; +import { useState } from 'react'; +import { ModalCloseButton } from '@/ui/modal'; +import router from 'next/router'; +import { useAppState } from '@/providers/app-state-provider'; + +interface WorkspaceCreateProps { + open: boolean; + onClose: () => void; +} + +const DefaultHeadImgColors = [ + ['#C6F2F3', '#0C6066'], + ['#FFF5AB', '#896406'], + ['#FFCCA7', '#8F4500'], + ['#FFCECE', '#AF1212'], + ['#E3DEFF', '#511AAB'], +]; + +export const WorkspaceCreate = ({ open, onClose }: WorkspaceCreateProps) => { + const [workspaceName, setWorkspaceId] = useState(''); + const [creating, setCreating] = useState(false); + const { refreshWorkspacesMeta } = useAppState(); + const handlerInputChange = (workspaceName: string) => { + setWorkspaceId(workspaceName); + }; + const createDefaultHeadImg = (workspaceName: string) => { + const canvas = document.createElement('canvas'); + canvas.height = 100; + canvas.width = 100; + const ctx = canvas.getContext('2d'); + return new Promise((resolve, reject) => { + if (ctx) { + const randomNumber = Math.floor(Math.random() * 5); + const randomColor = DefaultHeadImgColors[randomNumber]; + ctx.fillStyle = randomColor[0]; + ctx.fillRect(0, 0, 100, 100); + ctx.font = "600 50px 'PingFang SC', 'Microsoft Yahei'"; + ctx.fillStyle = randomColor[1]; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.fillText(workspaceName[0], 50, 50); + canvas.toBlob(blob => { + if (blob) { + const blobId = uploadBlob({ blob }); + resolve(blobId); + } else { + reject(); + } + }, 'image/png'); + } else { + reject(); + } + }); + }; + const handleCreateWorkspace = async () => { + setCreating(true); + const blobId = await createDefaultHeadImg(workspaceName).catch(() => { + setCreating(false); + }); + if (blobId) { + createWorkspace({ name: workspaceName, avatar: blobId }) + .then(async data => { + await refreshWorkspacesMeta(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + router.push(`/workspace/${data.id}`); + onClose(); + }) + .catch(err => { + console.log(err, 'err'); + }) + .finally(() => { + setCreating(false); + }); + } + }; + return ( + + + + Create new Workspace + + Workspaces are shared environments where teams can collaborate. After + creating a Workspace, you can invite others to join. + + + + + + + Create + + + + + ); +}; + +export default WorkspaceCreate; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/ListItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/ListItem.tsx new file mode 100644 index 0000000000..120a003953 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/ListItem.tsx @@ -0,0 +1,37 @@ +import type { ReactNode } from 'react'; +import { styled } from '@/styles'; +import { WorkspaceItemWrapper, WorkspaceItemContent } from './styles'; + +interface ListItemProps { + name: string; + icon: ReactNode; + onClick: () => void; +} + +export const ListItem = ({ name, icon, onClick }: ListItemProps) => { + return ( + + {icon} + + {name} + + + ); +}; + +const Name = styled('div')(({ theme }) => { + return { + color: theme.colors.quoteColor, + fontSize: theme.font.sm, + fontWeight: 400, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); + +const StyledIconWrapper = styled('div')({ + width: '20px', + height: '20px', + fontSize: '20px', +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx new file mode 100644 index 0000000000..66b093e64c --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/LoginItem.tsx @@ -0,0 +1,52 @@ +import { useModal } from '@/providers/global-modal-provider'; +import { styled } from '@/styles'; +import { AffineIcon } from '../../icons/icons'; +import { + WorkspaceItemAvatar, + LoginItemWrapper, + WorkspaceItemContent, +} from './styles'; + +export const LoginItem = () => { + const { triggerLoginModal } = useModal(); + return ( + triggerLoginModal()} + data-testid="open-login-modal" + > + + + + + AFFiNE + + Log in to sync with affine + + + + ); +}; + +const Name = styled('div')(({ theme }) => { + return { + color: theme.colors.quoteColor, + fontSize: theme.font.base, + fontWeight: 500, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); + +const Description = styled('div')(({ theme }) => { + return { + color: theme.colors.iconColor, + fontSize: theme.font.sm, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/PrivateWorkspaceItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/PrivateWorkspaceItem.tsx new file mode 100644 index 0000000000..1301200f79 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/PrivateWorkspaceItem.tsx @@ -0,0 +1,60 @@ +import { styled } from '@/styles'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { + WorkspaceItemAvatar, + PrivateWorkspaceWrapper, + WorkspaceItemContent, +} from './styles'; +import { useRouter } from 'next/router'; + +type PrivateWorkspaceItemProps = { + privateWorkspaceId?: string; +}; + +export const PrivateWorkspaceItem = ({ + privateWorkspaceId, +}: PrivateWorkspaceItemProps) => { + const { user } = useAppState(); + const router = useRouter(); + const handleClick = () => { + if (privateWorkspaceId) { + router.push(`/workspace/${privateWorkspaceId}`); + } + }; + if (user) { + const Username = user.name; + return ( + + + {Username} + + + {Username} + {user.email} + + + ); + } + return null; +}; + +const Name = styled('div')(({ theme }) => { + return { + color: theme.colors.quoteColor, + fontSize: theme.font.base, + fontWeight: 500, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); + +const Email = styled('div')(({ theme }) => { + return { + color: theme.colors.iconColor, + fontSize: theme.font.sm, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx new file mode 100644 index 0000000000..eb761058b6 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterSetting.tsx @@ -0,0 +1,32 @@ +import { SettingsIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { IconButton } from '@/ui/button'; +import { MouseEventHandler } from 'react'; + +type SettingProps = { + onClick?: () => void; +}; + +export const FooterSetting = ({ onClick }: SettingProps) => { + const handleClick: MouseEventHandler = e => { + e.stopPropagation(); + onClick && onClick(); + }; + return ( + { + e.stopPropagation(); + handleClick(e); + }} + > + + + ); +}; + +const Wrapper = styled(IconButton)(() => { + return { + fontSize: '20px', + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx new file mode 100644 index 0000000000..c241555877 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/FooterUsers.tsx @@ -0,0 +1,29 @@ +import { UsersIcon } from '@blocksuite/icons'; +import { styled } from '@/styles'; +import { IconButton } from '@/ui/button'; + +type FooterUsersProps = { + memberCount: number; +}; + +export const FooterUsers = ({ memberCount = 1 }: FooterUsersProps) => { + return ( + + <> + + {memberCount > 99 ? '99+' : memberCount} + + + ); +}; + +const Wrapper = styled(IconButton)({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontSize: '16px', +}); + +const Tip = styled('span')({ + fontSize: '12px', +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx new file mode 100644 index 0000000000..12f0ba59ca --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/WorkspaceItem.tsx @@ -0,0 +1,96 @@ +import { useRouter } from 'next/router'; +import { styled } from '@/styles'; +import { + WorkspaceItemAvatar, + WorkspaceItemWrapper, + WorkspaceItemContent, +} from '../styles'; +import { FooterSetting } from './FooterSetting'; +import { FooterUsers } from './FooterUsers'; +import { WorkspaceType } from '@affine/data-services'; +import { useAppState } from '@/providers/app-state-provider'; + +interface WorkspaceItemProps { + id: string; + name: string; + icon: string; + type: WorkspaceType; + memberCount: number; + onClickSetting?: (workspaceId: string) => void; +} + +export const WorkspaceItem = ({ + id, + name, + icon, + type, + onClickSetting, + memberCount, +}: WorkspaceItemProps) => { + const router = useRouter(); + + const { currentWorkspaceId } = useAppState(); + + const handleClickSetting = async () => { + onClickSetting && onClickSetting(id); + }; + + return ( + { + router.push(`/workspace/${id}`); + }} + canSet={ + type !== WorkspaceType.Private && currentWorkspaceId === String(id) + } + > + + {name.charAt(0)} + + + {name} + +
+ + +
+
+ ); +}; + +const Name = styled('div')(({ theme }) => { + return { + color: theme.colors.quoteColor, + fontSize: theme.font.sm, + fontWeight: 400, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}); + +const StyledWrapper = styled(WorkspaceItemWrapper)<{ canSet: boolean }>( + ({ canSet }) => { + return { + '& .footer-setting': { + display: 'none', + }, + ':hover .footer-users': { + display: canSet ? 'none' : '', + }, + ':hover .footer-setting': { + display: canSet ? 'block' : 'none', + }, + }; + } +); + +const Footer = styled('div')({ + width: '42px', + flex: '0 42px', + fontSize: '20px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + marginLeft: '12px', +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/index.ts new file mode 100644 index 0000000000..60080d5a77 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/WorkspaceItem/index.ts @@ -0,0 +1 @@ +export * from './WorkspaceItem'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/index.ts new file mode 100644 index 0000000000..35bf8e4cf9 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/index.ts @@ -0,0 +1,5 @@ +export * from './PrivateWorkspaceItem'; +export * from './WorkspaceItem'; +export * from './CreateWorkspaceItem'; +export * from './ListItem'; +export * from './LoginItem'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/styles.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/styles.ts new file mode 100644 index 0000000000..add82c90c7 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceItem/styles.ts @@ -0,0 +1,41 @@ +import MuiAvatar from '@mui/material/Avatar'; +import { styled } from '@/styles'; + +export const WorkspaceItemWrapper = styled('div')(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + cursor: 'pointer', + borderRadius: '5px', + padding: '6px 12px', + + ':hover': { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, +})); + +export const PrivateWorkspaceWrapper = styled(WorkspaceItemWrapper)({ + padding: '10px 12px', +}); + +export const LoginItemWrapper = styled(WorkspaceItemWrapper)(({ theme }) => { + return { + padding: '10px 12px', + + ':hover .login-description': { + color: theme.colors.primaryColor, + }, + }; +}); + +export const WorkspaceItemAvatar = styled(MuiAvatar)({ + height: '40px', + width: '40px', +}); + +export const WorkspaceItemContent = styled('div')({ + minWidth: 0, + marginLeft: '12px', + flexGrow: 1, +}); diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx new file mode 100644 index 0000000000..0e574cebbd --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/WorkspaceSelector.tsx @@ -0,0 +1,53 @@ +import { Popper } from '@/ui/popper'; +import { Avatar, WorkspaceName, SelectorWrapper } from './styles'; +import { SelectorPopperContent } from './SelectorPopperContent'; +import { useState } from 'react'; +import { useAppState } from '@/providers/app-state-provider'; +import { WorkspaceType } from '@affine/data-services'; +import { AffineIcon } from '../icons/icons'; + +export const WorkspaceSelector = () => { + const [isShow, setIsShow] = useState(false); + const { currentWorkspace, workspacesMeta, currentWorkspaceId, user } = + useAppState(); + const workspaceMeta = workspacesMeta.find( + meta => String(meta.id) === String(currentWorkspaceId) + ); + return ( + } + zIndex={1000} + placement="bottom-start" + trigger="click" + onVisibleChange={setIsShow} + > + + + {workspaceMeta?.type === WorkspaceType.Private && user ? ( + user?.name[0] + ) : ( + + )} + + + {workspaceMeta?.type === WorkspaceType.Private + ? user + ? user.name + : 'AFFiNE' + : currentWorkspace?.meta.name || 'AFFiNE'} + + + + ); +}; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/index.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/index.ts new file mode 100644 index 0000000000..7de8a3ee0a --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/index.ts @@ -0,0 +1 @@ +export * from './WorkspaceSelector'; diff --git a/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/styles.ts b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/styles.ts new file mode 100644 index 0000000000..e9c231c69e --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/WorkspaceSelector/styles.ts @@ -0,0 +1,41 @@ +import MuiAvatar from '@mui/material/Avatar'; +import { styled } from '@/styles'; +import { StyledPopperContainer } from '@/ui/shared/Container'; + +export const SelectorWrapper = styled('div')({ + width: '100%', + height: '100%', + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + ':hover': { + cursor: 'pointer', + }, +}); + +export const Avatar = styled(MuiAvatar)({ + height: '28px', + width: '28px', +}); + +export const WorkspaceName = styled('span')(({ theme }) => { + return { + marginLeft: '12px', + lineHeight: 1, + fontSize: '18px', + fontWeight: 500, + color: theme.colors.iconColor, + }; +}); + +export const SelectorPopperContainer = styled(StyledPopperContainer)( + ({ theme }) => { + return { + width: '334px', + boxShadow: theme.shadow.tooltip, + padding: '24px 12px', + backgroundColor: theme.colors.pageBackground, + fontSize: theme.font.xs, + }; + } +); diff --git a/packages/app/src/components/workspace-slider-bar/icons.tsx b/packages/app/src/components/workspace-slider-bar/icons.tsx new file mode 100644 index 0000000000..160dac720d --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/icons.tsx @@ -0,0 +1,13 @@ +export const Arrow = () => { + return ( + + + + ); +}; diff --git a/packages/app/src/components/workspace-slider-bar/icons/icons.tsx b/packages/app/src/components/workspace-slider-bar/icons/icons.tsx new file mode 100644 index 0000000000..2fee60b0f5 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/icons/icons.tsx @@ -0,0 +1,27 @@ +export const AffineIcon = () => { + return ( + + + + + ); +}; diff --git a/packages/app/src/components/workspace-slider-bar/index.tsx b/packages/app/src/components/workspace-slider-bar/index.tsx new file mode 100644 index 0000000000..44e02ea84e --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/index.tsx @@ -0,0 +1,188 @@ +import React, { useState } from 'react'; +import { useRouter } from 'next/router'; +import { + StyledArrowButton, + StyledLink, + StyledListItem, + // StyledListItemForWorkspace, + StyledNewPageButton, + StyledQuickSearch, + StyledSliderBar, + StyledSliderBarWrapper, + StyledSubListItem, +} from './style'; +import { Arrow } from './icons'; +import Collapse from '@mui/material/Collapse'; +import { + ArrowDownIcon, + SearchIcon, + AllPagesIcon, + FavouritesIcon, + ImportIcon, + TrashIcon, + AddIcon, +} from '@blocksuite/icons'; +import Link from 'next/link'; +import { Tooltip } from '@/ui/tooltip'; +import { useModal } from '@/providers/global-modal-provider'; +import { useAppState } from '@/providers/app-state-provider/context'; + +import { IconButton } from '@/ui/button'; +// import { WorkspaceSelector } from './WorkspaceSelector'; +import useLocalStorage from '@/hooks/use-local-storage'; +import usePageMetaList from '@/hooks/use-page-meta-list'; +import { usePageHelper } from '@/hooks/use-page-helper'; +import { getUaHelper } from '@/utils'; + +const isMac = () => { + return getUaHelper().isMacOs; +}; +const FavoriteList = ({ showList }: { showList: boolean }) => { + const { openPage } = usePageHelper(); + const pageList = usePageMetaList(); + const router = useRouter(); + + const favoriteList = pageList.filter(p => p.favorite && !p.trash); + return ( + + {favoriteList.map((pageMeta, index) => { + const active = router.query.pageId === pageMeta.id; + return ( + { + if (active) { + return; + } + openPage(pageMeta.id); + }} + > + {pageMeta.title || 'Untitled'} + + ); + })} + {favoriteList.length === 0 && ( + No item + )} + + ); +}; +export const WorkSpaceSliderBar = () => { + const { triggerQuickSearchModal } = useModal(); + const [showSubFavorite, setShowSubFavorite] = useState(true); + const { currentWorkspaceId } = useAppState(); + const { openPage, createPage } = usePageHelper(); + const router = useRouter(); + + const [showTip, setShowTip] = useState(false); + const [show, setShow] = useLocalStorage('AFFiNE_SLIDE_BAR', false, true); + + const paths = { + all: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/all` : '', + favorite: currentWorkspaceId + ? `/workspace/${currentWorkspaceId}/favorite` + : '', + trash: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '', + }; + + return ( + <> + + + { + setShow(!show); + setShowTip(false); + }} + onMouseEnter={() => { + setShowTip(true); + }} + onMouseLeave={() => { + setShowTip(false); + }} + > + + + + + + {/* + + */} + { + triggerQuickSearchModal(); + }} + > + + Quick search + {isMac() ? '⌘ + K' : 'Ctrl + K'} + + + + All pages + + + + + + Favourites + + { + setShowSubFavorite(!showSubFavorite); + }} + > + + + + + + + { + // triggerImportModal(); + }} + > + Import + + + + + + Trash + + + { + const pageId = await createPage(); + if (pageId) { + openPage(pageId); + } + }} + > + New Page + + + + + ); +}; + +export default WorkSpaceSliderBar; diff --git a/packages/app/src/components/workspace-slider-bar/style.ts b/packages/app/src/components/workspace-slider-bar/style.ts new file mode 100644 index 0000000000..3ee270f3d4 --- /dev/null +++ b/packages/app/src/components/workspace-slider-bar/style.ts @@ -0,0 +1,174 @@ +import { displayFlex, styled, textEllipsis } from '@/styles'; +import Link from 'next/link'; + +export const StyledSliderBar = styled.div<{ show: boolean }>( + ({ theme, show }) => { + return { + width: show ? '320px' : '0', + height: '100vh', + background: theme.mode === 'dark' ? '#272727' : '#FBFBFC', + boxShadow: theme.shadow.modal, + transition: 'width .15s, padding .15s', + position: 'relative', + zIndex: theme.zIndex.modal, + padding: show ? '24px 12px' : '24px 0', + flexShrink: 0, + }; + } +); +export const StyledSliderBarWrapper = styled.div(() => { + return { + height: '100%', + overflowX: 'hidden', + overflowY: 'auto', + }; +}); + +export const StyledArrowButton = styled.button<{ isShow: boolean }>( + ({ theme, isShow }) => { + return { + width: '32px', + height: '32px', + ...displayFlex('center', 'center'), + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + borderRadius: '50%', + transition: 'all .15s', + position: 'absolute', + top: '34px', + right: '-20px', + zIndex: theme.zIndex.modal, + svg: { + transform: isShow ? 'rotate(180deg)' : 'unset', + }, + ':hover': { + color: '#fff', + backgroundColor: theme.colors.primaryColor, + }, + }; + } +); + +export const StyledListItem = styled.div<{ + active?: boolean; + disabled?: boolean; +}>(({ theme, active, disabled }) => { + return { + width: '296px', + height: '32px', + marginTop: '12px', + fontSize: theme.font.sm, + color: active ? theme.colors.primaryColor : theme.colors.popoverColor, + paddingLeft: '12px', + borderRadius: '5px', + ...displayFlex('flex-start', 'center'), + ...(disabled + ? { + cursor: 'not-allowed', + color: theme.colors.borderColor, + } + : {}), + + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + ':hover:not([disabled])': { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; +}); + +export const StyledListItemForWorkspace = styled(StyledListItem)({ + height: '52px', +}); + +export const StyledLink = styled(Link)(() => { + return { + flexGrow: 1, + textAlign: 'left', + color: 'inherit', + ...displayFlex('flex-start', 'center'), + ':visited': { + color: 'inherit', + }, + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + }; +}); +export const StyledNewPageButton = styled(StyledListItem)(() => { + return { + position: 'absolute', + bottom: '24px', + left: '0', + right: '0', + margin: 'auto', + ':hover': { + cursor: 'pointer', + }, + }; +}); + +export const StyledSubListItem = styled.button<{ + disable?: boolean; + active?: boolean; +}>(({ theme, disable, active }) => { + return { + width: '296px', + height: '32px', + marginTop: '4px', + fontSize: theme.font.sm, + color: disable + ? theme.colors.iconColor + : active + ? theme.colors.primaryColor + : theme.colors.popoverColor, + backgroundColor: active ? theme.colors.hoverBackground : 'unset', + + cursor: disable ? 'not-allowed' : 'pointer', + paddingLeft: '45px', + lineHeight: '32px', + textAlign: 'start', + ...textEllipsis(1), + ':hover': disable + ? {} + : { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; +}); + +export const StyledQuickSearch = styled.div(({ theme }) => { + return { + width: '296px', + height: '32px', + marginTop: '12px', + fontSize: theme.font.sm, + backgroundColor: theme.colors.hoverBackground, + color: theme.colors.popoverColor, + paddingLeft: '12px', + borderRadius: '5px', + ...displayFlex('flex-start', 'center'), + '>svg': { + fontSize: '20px', + marginRight: '12px', + }, + '>span': { + fontSize: theme.font.xs, + margin: 'auto', + marginRight: '12px', + color: theme.colors.hoverBackground, + transition: 'all .15s', + }, + ':hover': { + color: theme.colors.popoverColor, + '>span': { + color: theme.colors.popoverColor, + }, + }, + }; +}); diff --git a/packages/app/src/globals.d.ts b/packages/app/src/globals.d.ts new file mode 100644 index 0000000000..60228d4359 --- /dev/null +++ b/packages/app/src/globals.d.ts @@ -0,0 +1,4 @@ +declare module '*.md' { + const text: string; + export default text; +} diff --git a/packages/app/src/hooks/use-change-page-meta.ts b/packages/app/src/hooks/use-change-page-meta.ts new file mode 100644 index 0000000000..1b553f2099 --- /dev/null +++ b/packages/app/src/hooks/use-change-page-meta.ts @@ -0,0 +1,22 @@ +import { useCallback } from 'react'; +import { useAppState, PageMeta } from '@/providers/app-state-provider'; + +export type ChangePageMeta = ( + pageId: string, + pageMeta: Partial +) => void; + +export const useChangePageMeta = () => { + const { currentWorkspace } = useAppState(); + + return useCallback( + (pageId, pageMeta) => { + currentWorkspace?.setPageMeta(pageId, { + ...pageMeta, + }); + }, + [currentWorkspace] + ); +}; + +export default ChangePageMeta; diff --git a/packages/app/src/hooks/use-current-page-meta.ts b/packages/app/src/hooks/use-current-page-meta.ts new file mode 100644 index 0000000000..14db9ae2ab --- /dev/null +++ b/packages/app/src/hooks/use-current-page-meta.ts @@ -0,0 +1,37 @@ +import { useCallback, useEffect, useState } from 'react'; +import { useAppState, PageMeta } from '@/providers/app-state-provider'; + +export const useCurrentPageMeta = (): PageMeta | null => { + const { currentPage, currentWorkspace } = useAppState(); + + const pageMetaHandler = useCallback((): PageMeta | null => { + if (!currentPage || !currentWorkspace) { + return null; + } + return ( + (currentWorkspace.meta.pageMetas.find( + p => p.id === currentPage.id + ) as PageMeta) ?? null + ); + }, [currentPage, currentWorkspace]); + + const [currentPageMeta, setCurrentPageMeta] = useState( + pageMetaHandler + ); + + useEffect(() => { + setCurrentPageMeta(pageMetaHandler); + + const dispose = currentWorkspace?.meta.pagesUpdated.on(() => { + setCurrentPageMeta(pageMetaHandler); + }).dispose; + + return () => { + dispose?.(); + }; + }, [currentPage, currentWorkspace, pageMetaHandler]); + + return currentPageMeta; +}; + +export default useCurrentPageMeta; diff --git a/packages/app/src/hooks/use-ensure-workspace.ts b/packages/app/src/hooks/use-ensure-workspace.ts new file mode 100644 index 0000000000..848c1cc428 --- /dev/null +++ b/packages/app/src/hooks/use-ensure-workspace.ts @@ -0,0 +1,56 @@ +import { useState, useEffect } from 'react'; +import { useAppState } from '@/providers/app-state-provider'; +import { useRouter } from 'next/router'; +const defaultOutLineWorkspaceId = + 'local-first-' + '85b4ca0b9081421d903bbc2501ea280f'; +// It is a fully effective hook +// Cause it not just ensure workspace loaded, but also have router change. +export const useEnsureWorkspace = () => { + const [workspaceLoaded, setWorkspaceLoaded] = useState(false); + const { workspacesMeta, loadWorkspace, synced, user } = useAppState(); + const router = useRouter(); + + // const defaultOutLineWorkspaceId = '99ce7eb7'; + // console.log(defaultOutLineWorkspaceId); + useEffect(() => { + if (!synced) { + setWorkspaceLoaded(false); + return; + } + // If router.query.workspaceId is not in workspace list, jump to 404 page + // If workspacesMeta is empty, we need to create a default workspace but not jump to 404 + if ( + workspacesMeta.length && + router.query.workspaceId && + workspacesMeta.findIndex( + meta => meta.id.toString() === router.query.workspaceId + ) === -1 + ) { + router.push('/404'); + return; + } + // If user is not login and input a custom workspaceId, jump to 404 page + if ( + !user && + router.query.workspaceId && + router.query.workspaceId !== defaultOutLineWorkspaceId + ) { + router.push('/404'); + return; + } + + const workspaceId = user + ? (router.query.workspaceId as string) || workspacesMeta?.[0]?.id + : defaultOutLineWorkspaceId; + + loadWorkspace(workspaceId).finally(() => { + setWorkspaceLoaded(true); + }); + }, [loadWorkspace, router, synced, user, workspacesMeta]); + + return { + workspaceLoaded, + }; +}; + +export default useEnsureWorkspace; diff --git a/packages/app/src/hooks/use-history-update.ts b/packages/app/src/hooks/use-history-update.ts new file mode 100644 index 0000000000..8329988e0c --- /dev/null +++ b/packages/app/src/hooks/use-history-update.ts @@ -0,0 +1,37 @@ +import { Page } from '@blocksuite/store'; +import { useAppState } from '@/providers/app-state-provider'; +import { useEffect, useRef } from 'react'; + +export type EventCallBack = (callback: (props: T) => void) => void; +export type UseHistoryUpdated = (page?: Page) => EventCallBack; + +export const useHistoryUpdate: UseHistoryUpdated = () => { + const { currentPage } = useAppState(); + + const callbackQueue = useRef<((page: Page) => void)[]>([]); + + useEffect(() => { + if (!currentPage) { + return; + } + + setTimeout(() => { + currentPage.signals.historyUpdated.on(() => { + callbackQueue.current.forEach(callback => { + callback(currentPage); + }); + }); + }, 300); + + return () => { + callbackQueue.current = []; + currentPage.signals.historyUpdated.dispose(); + }; + }, [currentPage]); + + return callback => { + callbackQueue.current.push(callback); + }; +}; + +export default useHistoryUpdate; diff --git a/packages/app/src/hooks/use-init-workspace.ts b/packages/app/src/hooks/use-init-workspace.ts new file mode 100644 index 0000000000..1866e69b4c --- /dev/null +++ b/packages/app/src/hooks/use-init-workspace.ts @@ -0,0 +1,38 @@ +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { useEffect, useRef, useState } from 'react'; + +export const useInitWorkspace = (disabled?: boolean) => { + const [loading, setLoading] = useState(true); + // Do not set as a constant, or it will trigger a hell of re-rendering + const defaultOutLineWorkspaceId = useRef(new Date().getTime().toString()); + + const router = useRouter(); + const { + workspacesMeta, + loadWorkspace, + currentWorkspace, + currentWorkspaceId, + } = useAppState(); + const workspaceId = + (router.query.workspaceId as string) || + workspacesMeta?.[0]?.id || + defaultOutLineWorkspaceId.current; + + useEffect(() => { + if (disabled) { + setLoading(false); + return; + } + setLoading(true); + loadWorkspace(workspaceId).finally(() => { + setLoading(false); + }); + }, [workspaceId, disabled, loadWorkspace]); + + return { + workspaceId, + workspace: workspaceId === currentWorkspaceId ? currentWorkspace : null, + loading, + }; +}; diff --git a/packages/app/src/hooks/use-local-storage.ts b/packages/app/src/hooks/use-local-storage.ts new file mode 100644 index 0000000000..ee4e630c6a --- /dev/null +++ b/packages/app/src/hooks/use-local-storage.ts @@ -0,0 +1,33 @@ +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; + +export type UseLocalStorage = ( + key: string, + initialState: S | (() => S), + // If initialState is different from the initial local data, set it + initialValue?: S +) => [S, Dispatch>]; + +export const useLocalStorage: UseLocalStorage = ( + key, + defaultValue, + initialValue +) => { + const [item, setItem] = useState(defaultValue); + + useEffect(() => { + const saved = localStorage.getItem(key); + if (saved) { + setItem(JSON.parse(saved)); + } else if (initialValue) { + setItem(initialValue); + } + }, [initialValue, key, setItem]); + + useEffect(() => { + localStorage.setItem(key, JSON.stringify(item)); + }, [item, key]); + + return [item, setItem]; +}; + +export default useLocalStorage; diff --git a/packages/app/src/hooks/use-page-helper.ts b/packages/app/src/hooks/use-page-helper.ts new file mode 100644 index 0000000000..e4bf4f2957 --- /dev/null +++ b/packages/app/src/hooks/use-page-helper.ts @@ -0,0 +1,127 @@ +import { Workspace, uuidv4 } from '@blocksuite/store'; +import { QueryContent } from '@blocksuite/store/dist/workspace/search'; +import { PageMeta, useAppState } from '@/providers/app-state-provider'; +import { EditorContainer } from '@blocksuite/editor'; +import { useChangePageMeta } from '@/hooks/use-change-page-meta'; +import { useRouter } from 'next/router'; + +export type EditorHandlers = { + createPage: (params?: { + pageId?: string; + title?: string; + }) => Promise; + openPage: ( + pageId: string, + query?: { [key: string]: string }, + newTab?: boolean + ) => Promise; + getPageMeta: (pageId: string) => PageMeta | null; + toggleDeletePage: (pageId: string) => Promise; + toggleFavoritePage: (pageId: string) => Promise; + permanentlyDeletePage: (pageId: string) => void; + search: (query: QueryContent) => Map; + // changeEditorMode: (pageId: string) => void; + changePageMode: ( + pageId: string, + mode: EditorContainer['mode'] + ) => Promise; +}; + +const getPageMeta = (workspace: Workspace | null, pageId: string) => { + return workspace?.meta.pageMetas.find(p => p.id === pageId); +}; +export const usePageHelper = (): EditorHandlers => { + const router = useRouter(); + const changePageMeta = useChangePageMeta(); + const { currentWorkspace, editor, currentWorkspaceId } = useAppState(); + + return { + createPage: ({ + pageId = uuidv4().replaceAll('-', ''), + title = '', + } = {}) => { + return new Promise(resolve => { + if (!currentWorkspace) { + return resolve(null); + } + currentWorkspace.createPage(pageId); + currentWorkspace.signals.pageAdded.once(addedPageId => { + currentWorkspace.setPageMeta(addedPageId, { title }); + resolve(addedPageId); + }); + }); + }, + toggleFavoritePage: async pageId => { + const pageMeta = getPageMeta(currentWorkspace, pageId); + if (!pageMeta) { + return Promise.reject('No page'); + } + const favorite = !pageMeta.favorite; + changePageMeta(pageMeta.id, { + favorite, + }); + return favorite; + }, + toggleDeletePage: async pageId => { + const pageMeta = getPageMeta(currentWorkspace, pageId); + + if (!pageMeta) { + return Promise.reject('No page'); + } + const trash = !pageMeta.trash; + + changePageMeta(pageMeta.id, { + trash, + trashDate: +new Date(), + }); + return trash; + }, + search: (query: QueryContent) => { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return currentWorkspace!.search(query); + }, + changePageMode: async (pageId, mode) => { + const pageMeta = getPageMeta(currentWorkspace, pageId); + if (!pageMeta) { + return Promise.reject('No page'); + } + + editor?.setAttribute('mode', mode as string); + + changePageMeta(pageMeta.id, { + mode, + }); + return mode; + }, + permanentlyDeletePage: pageId => { + // TODO: workspace.meta.removePage or workspace.removePage? + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + currentWorkspace!.meta.removePage(pageId); + }, + openPage: (pageId, query = {}, newTab = false) => { + pageId = pageId.replace('space:', ''); + + if (newTab) { + window.open(`/workspace/${currentWorkspaceId}/${pageId}`, '_blank'); + return Promise.resolve(true); + } + return router.push({ + pathname: `/workspace/${currentWorkspaceId}/${pageId}`, + query, + }); + }, + getPageMeta: pageId => { + if (!currentWorkspace) { + return null; + } + + return ( + (currentWorkspace.meta.pageMetas.find( + page => page.id === pageId + ) as PageMeta) || null + ); + }, + }; +}; + +export default usePageHelper; diff --git a/packages/app/src/hooks/use-page-meta-list.ts b/packages/app/src/hooks/use-page-meta-list.ts new file mode 100644 index 0000000000..b500cca7ba --- /dev/null +++ b/packages/app/src/hooks/use-page-meta-list.ts @@ -0,0 +1,25 @@ +import { useEffect, useState } from 'react'; +import { PageMeta } from '@/providers/app-state-provider'; +import { useAppState } from '@/providers/app-state-provider'; + +export const usePageMetaList = () => { + const { currentWorkspace } = useAppState(); + const [pageList, setPageList] = useState([]); + + useEffect(() => { + if (!currentWorkspace) { + return; + } + setPageList(currentWorkspace.meta.pageMetas as PageMeta[]); + const dispose = currentWorkspace.meta.pagesUpdated.on(() => { + setPageList(currentWorkspace.meta.pageMetas as PageMeta[]); + }).dispose; + return () => { + dispose(); + }; + }, [currentWorkspace]); + + return pageList; +}; + +export default usePageMetaList; diff --git a/packages/app/src/hooks/use-props-updated.ts b/packages/app/src/hooks/use-props-updated.ts new file mode 100644 index 0000000000..d76ada43d2 --- /dev/null +++ b/packages/app/src/hooks/use-props-updated.ts @@ -0,0 +1,38 @@ +import { useEffect, useRef } from 'react'; +import { EditorContainer } from '@blocksuite/editor'; +import { useAppState } from '@/providers/app-state-provider'; +export type EventCallBack = (callback: (props: T) => void) => void; + +export type UsePropsUpdated = ( + editor?: EditorContainer +) => EventCallBack; + +export const usePropsUpdated: UsePropsUpdated = () => { + const { editor } = useAppState(); + + const callbackQueue = useRef<((editor: EditorContainer) => void)[]>([]); + + useEffect(() => { + if (!editor) { + return; + } + setTimeout(() => { + editor.model?.propsUpdated.on(() => { + callbackQueue.current.forEach(callback => { + callback(editor); + }); + }); + }, 300); + + return () => { + callbackQueue.current = []; + editor?.model?.propsUpdated.dispose(); + }; + }, [editor]); + + return callback => { + callbackQueue.current.push(callback); + }; +}; + +export default usePropsUpdated; diff --git a/packages/app/src/libs/i18n/index.ts b/packages/app/src/libs/i18n/index.ts new file mode 100644 index 0000000000..fe6d76ab41 --- /dev/null +++ b/packages/app/src/libs/i18n/index.ts @@ -0,0 +1,66 @@ +import i18next, { Resource } from 'i18next'; +import { + I18nextProvider, + initReactI18next, + useTranslation, +} from 'react-i18next'; +import { LOCALES } from './resources'; +import type en_US from './resources/en.json'; + +// const localStorage = { +// getItem() { +// return undefined; +// }, +// setItem() {}, +// }; +// See https://react.i18next.com/latest/typescript +declare module 'react-i18next' { + interface CustomTypeOptions { + // custom namespace type if you changed it + // defaultNS: 'ns1'; + // custom resources type + resources: { + en: typeof en_US; + }; + } +} + +// const STORAGE_KEY = 'i18n_lng'; + +export { i18n, useTranslation, I18nProvider, LOCALES }; + +const resources = LOCALES.reduce( + (acc, { tag, res }) => ({ ...acc, [tag]: { translation: res } }), + {} +); + +const fallbackLng = LOCALES[0].tag; +const standardizeLocale = (language: string) => { + if (LOCALES.find(locale => locale.tag === language)) return language; + if (LOCALES.find(locale => locale.tag === language.slice(0, 2).toLowerCase())) + return language; + return fallbackLng; +}; + +const language = standardizeLocale( + // localStorage.getItem(STORAGE_KEY) ?? + // (typeof navigator !== 'undefined' ? navigator.language : 'en') + 'en' +); + +const i18n = i18next.createInstance(); +i18n.use(initReactI18next).init({ + lng: language, + fallbackLng, + debug: false, + resources, + interpolation: { + escapeValue: false, // not needed for react as it escapes by default + }, +}); + +i18n.on('languageChanged', () => { + // localStorage.setItem(STORAGE_KEY, lng); +}); + +const I18nProvider = I18nextProvider; diff --git a/packages/app/src/libs/i18n/resources/bn.json b/packages/app/src/libs/i18n/resources/bn.json new file mode 100644 index 0000000000..5eb50f8cdf --- /dev/null +++ b/packages/app/src/libs/i18n/resources/bn.json @@ -0,0 +1,22 @@ +{ + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "", + "Add A Below Block": "নীচে একটি ব্লক যোগ করুন", + "WarningTips": { + "IsNotfsApiSupported": "অ্যাফাইন ডেমোতে স্বাগতম। পরিবর্তনগুলি সংরক্ষণ করা শুরু করতে আপনি Chrome/Edge এর মতো ক্রোমিয়াম ভিত্তিক ব্রাউজারের সর্বশেষ সংস্করণের মাধ্যমে ডিস্কে ডেটা সিঙ্ক করতে পারেন", + "DoNotStore": "অ্যাফাইন সক্রিয় ডেভেলপমেন্ট এর অধীনে এবং বর্তমান সংস্করণটি অস্থিতিশীল। দয়া করে কোন তথ্য বা ডেটা সঞ্চয় করবেন না" + }, + "Language": "ভাষা", + "Settings": "সেটিংস", + "Share": "শেয়ার করুন", + "Comment": "মন্তব্য", + "Delete": "মুছে ফেলুন", + "Copy Page Link": "পেজ লিংক কপি করুন", + "Duplicate Page": "সদৃশ পৃষ্ঠা তৈরি করুন", + "Logout": "লগআউট", + "Divide Here As A New Group": "একটি নতুন গ্রুপ হিসেবে বিভক্ত করুন", + "ComingSoon": "লেআউট সেটিংস শীঘ্রই আসছে...", + "Clear Workspace": "ওয়ার্কস্পেস পরিষ্কার করুন", + "Layout": "লেআউট", + "Turn into": "রূপান্তর করুন", + "Sync to Disk": "ডিস্ক এ সিঙ্ক করুন" +} diff --git a/packages/app/src/libs/i18n/resources/en.json b/packages/app/src/libs/i18n/resources/en.json new file mode 100644 index 0000000000..c2716448f7 --- /dev/null +++ b/packages/app/src/libs/i18n/resources/en.json @@ -0,0 +1,28 @@ +{ + "Sync to Disk": "Sync to Disk", + "Share": "Share", + "WarningTips": { + "IsNotfsApiSupported": "Welcome to the AFFiNE demo. To begin saving changes you can SYNC DATA TO DISK with the latest version of Chromium based browser like Chrome/Edge", + "IsNotLocalWorkspace": "Welcome to the AFFiNE demo. To begin saving changes you can SYNC TO DISK.", + "DoNotStore": "AFFiNE is under active development and the current version is UNSTABLE. Please DO NOT store information or data" + }, + "Layout": "Layout", + "Comment": "Comment", + "Settings": "Settings", + "ComingSoon": "Layout Settings Coming Soon...", + "Duplicate Page": "Duplicate Page", + "Copy Page Link": "Copy Page Link", + "Language": "Language", + "Clear Workspace": "Clear Workspace", + "Export As Markdown": "Export As Markdown", + "Export As HTML": "Export As HTML", + "Export As PDF (Unsupported)": "Export As PDF (Unsupported)", + "Import Workspace": "Import Workspace", + "Export Workspace": "Export Workspace", + "Last edited by": "Last edited by {{name}}", + "Logout": "Logout", + "Delete": "Delete", + "Turn into": "Turn into", + "Add A Below Block": "Add A Below Block", + "Divide Here As A New Group": "Divide Here As A New Group" +} diff --git a/packages/app/src/libs/i18n/resources/fr.json b/packages/app/src/libs/i18n/resources/fr.json new file mode 100644 index 0000000000..d30fdcb133 --- /dev/null +++ b/packages/app/src/libs/i18n/resources/fr.json @@ -0,0 +1,29 @@ +{ + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "", + "ComingSoon": "Bientôt disponible", + "Duplicate Page": "Dupliquer la page", + "Copy Page Link": "Copier le lien de la page", + "Delete": "Supprimer", + "Comment": "Commentaire", + "Export As HTML": "Exporter en HTML", + "Export As Markdown": "Exporter en Markdown", + "Export As PDF (Unsupported)": "exporter en PDF (non supporté)", + "Logout": "Déconnexion", + "Export Workspace": "Exporter l'espace de travail", + "Import Workspace": "Importer l'espace de travail", + "Language": "Langue", + "Last edited by": "Dernière édition par {{name}}", + "Layout": "Mise en forme", + "Settings": "Réglages", + "Share": "Partager", + "Sync to Disk": "Synchroniser sur le disque", + "Turn into": "Transformer en", + "WarningTips": { + "DoNotStore": "Affine est en développement actif ; la version actuelle est INSTABLE. Veuillez NE PAS stocker d'informations ou de données", + "IsNotLocalWorkspace": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE", + "IsNotfsApiSupported": "Bienvenue sur la démo d'AFFiNE. Pour commencer à sauvegarder vos modifications, vous pouvez SYNCHRONISER SUR LE DISQUE\navec la dernière version d'un navigateur basé sur Chromium tel que Chrome ou Edge." + }, + "Add A Below Block": "Ajouter un bloc en-dessous", + "Divide Here As A New Group": "Séparer ici en un nouveau groupe", + "Clear Workspace": "Vider l'espace de travail" +} diff --git a/packages/app/src/libs/i18n/resources/index.ts b/packages/app/src/libs/i18n/resources/index.ts new file mode 100644 index 0000000000..329fa9c679 --- /dev/null +++ b/packages/app/src/libs/i18n/resources/index.ts @@ -0,0 +1,72 @@ +// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +// Run `pnpm run download-resources` to regenerate. +// To overwrite this, please overwrite download.ts +import en from './en.json'; +import zh_Hans from './zh-Hans.json'; +import zh_Hant from './zh-Hant.json'; +import sr from './sr.json'; +import fr from './fr.json'; +import bn from './bn.json'; + +export const LOCALES = [ + { + id: 1000016008, + name: 'English', + tag: 'en', + originalName: 'English', + flagEmoji: '🇬🇧', + base: true, + completeRate: 1, + res: en, + }, + { + id: 1000016009, + name: 'Simplified Chinese', + tag: 'zh-Hans', + originalName: '简体中文', + flagEmoji: '🇨🇳', + base: false, + completeRate: 1, + res: zh_Hans, + }, + { + id: 1000016012, + name: 'Traditional Chinese', + tag: 'zh-Hant', + originalName: '繁體中文', + flagEmoji: '🇭🇰', + base: false, + completeRate: 1, + res: zh_Hant, + }, + { + id: 1000034005, + name: 'Serbian', + tag: 'sr', + originalName: 'српски', + flagEmoji: '🇷🇸', + base: false, + completeRate: 0.9166666666666666, + res: sr, + }, + { + id: 1000034008, + name: 'French', + tag: 'fr', + originalName: 'français', + flagEmoji: '🇫🇷', + base: false, + completeRate: 1, + res: fr, + }, + { + id: 1000034010, + name: 'Bangla', + tag: 'bn', + originalName: 'বাংলা', + flagEmoji: '🇧🇩', + base: false, + completeRate: 0.7083333333333334, + res: bn, + }, +] as const; diff --git a/packages/app/src/libs/i18n/resources/sr.json b/packages/app/src/libs/i18n/resources/sr.json new file mode 100644 index 0000000000..edc6c32f2c --- /dev/null +++ b/packages/app/src/libs/i18n/resources/sr.json @@ -0,0 +1,27 @@ +{ + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "", + "Clear Workspace": "Očisti radni prostor", + "ComingSoon": "Podešavanja za izgled dolaze", + "Comment": "Komentar", + "Copy Page Link": "Kopiraj link stranice", + "Delete": "Obriši", + "Duplicate Page": "Dupliraj stranicu", + "Export As HTML": "Izvezi kao HTML", + "Export As Markdown": "Izvezi kao Markdown", + "Export As PDF (Unsupported)": "Izvezi kao PDF (nepodržano)", + "Export Workspace": "Izvezi radnu površinu", + "Import Workspace": "Poboljšaj radnu površinu", + "Language": "Jezik", + "Last edited by": "Zadnju promenu uradio {{ime}}", + "Layout": "Izgled", + "Logout": "Odjava", + "Settings": "Podešavanja", + "Share": "Podeli", + "Sync to Disk": "Sinhroniziraj sa diskom", + "Turn into": "Promeni u", + "WarningTips": { + "DoNotStore": "AFFiNE je u stanju aktivnog razvoja i trenutna verzija je NESTABILNA. Molimo vas, NEMOJTE čuvati informacije ili podatke.", + "IsNotLocalWorkspace": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete kliknuti SINHRONIZUJ SA DISKOM.", + "IsNotfsApiSupported": "Dobrodošli u AFFiNE demo. Da bi započeli proces čuvanja promena možete SINHRONIZOVATI NA DISK sa poslednjom verzijom pretraživača tipa Chromium, kao što su Chrome/Edge." + } +} diff --git a/packages/app/src/libs/i18n/resources/zh-Hans.json b/packages/app/src/libs/i18n/resources/zh-Hans.json new file mode 100644 index 0000000000..14786dcb7e --- /dev/null +++ b/packages/app/src/libs/i18n/resources/zh-Hans.json @@ -0,0 +1,29 @@ +{ + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "", + "Sync to Disk": "同步到磁盘", + "Share": "分享", + "WarningTips": { + "IsNotfsApiSupported": "欢迎来到AFFiNE 的演示界面。您可以使用最新版本的基于Chrome的浏览器(如Chrome/Edge)将数据同步到磁盘来进行保存", + "IsNotLocalWorkspace": "欢迎来到AFFiNE 的演示界面,您可以同步到磁盘来进行保存操作。", + "DoNotStore": "AFFiNE 正在积极开发中,当前版本不稳定。请不要存储信息或数据。" + }, + "ComingSoon": "布局设置即将到来", + "Layout": "布局", + "Comment": "评论", + "Settings": "设置", + "Duplicate Page": "复制页面", + "Copy Page Link": "复制页面链接", + "Language": "当前语言", + "Clear Workspace": "清空工作区域", + "Export As Markdown": "导出 markdown", + "Export As HTML": "导出 HTML", + "Export As PDF (Unsupported)": "导出 PDF (暂不支持)", + "Import Workspace": "导入 Workspace", + "Export Workspace": "导出 Workspace", + "Last edited by": "最后编辑者为 {{name}}", + "Logout": "退出登录", + "Delete": "删除", + "Turn into": "转换为", + "Add A Below Block": "在下方添加一个新块", + "Divide Here As A New Group": "从这里划分一个新组" +} diff --git a/packages/app/src/libs/i18n/resources/zh-Hant.json b/packages/app/src/libs/i18n/resources/zh-Hant.json new file mode 100644 index 0000000000..4d0f28c9cd --- /dev/null +++ b/packages/app/src/libs/i18n/resources/zh-Hant.json @@ -0,0 +1,29 @@ +{ + "// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.": "", + "Add A Below Block": "在下方新添塊", + "Clear Workspace": "清空工作區", + "ComingSoon": "自定義佈局功能即將與您見面", + "Comment": "評論", + "Copy Page Link": "拷貝頁面鏈接", + "Delete": "刪除", + "Divide Here As A New Group": "從此地劃分成新組", + "Duplicate Page": "複製界面", + "Export As HTML": "導出 HTML", + "Export As Markdown": "以 Markdown 導出", + "Export As PDF (Unsupported)": "導出為 PDF(即將可用)", + "Export Workspace": "導出 Workspace", + "Import Workspace": "導入 Workspace", + "Language": "語言", + "Last edited by": "最後編輯者為 {{name}}", + "Layout": "佈局", + "Logout": "退出登錄", + "Settings": "設置", + "Share": "分享", + "Sync to Disk": "同步到磁盤", + "Turn into": "轉換為", + "WarningTips": { + "DoNotStore": "我們正在積極開發 AFFiNE,目前版本尚不穩定,請避免存儲信息或數據。", + "IsNotLocalWorkspace": "歡迎來到 AFFiNE 演示界面。您可以通過「同步到磁盤」來保存更改。", + "IsNotfsApiSupported": "歡迎進入AFFiNE演示!使用最新版本的基於 Chromium 內核的瀏覽器如Chrome/Edge,您可以通過「同步到磁盤」來保存更改" + } +} diff --git a/packages/app/src/libs/i18n/scripts/api.ts b/packages/app/src/libs/i18n/scripts/api.ts new file mode 100644 index 0000000000..0cd1a156b4 --- /dev/null +++ b/packages/app/src/libs/i18n/scripts/api.ts @@ -0,0 +1,185 @@ +// cSpell:ignore Tolgee +import { fetchTolgee } from './request'; + +/** + * Returns all project languages + * + * See https://tolgee.io/api#operation/getAll_6 + * @example + * ```ts + * const languages = [ + * { + * id: 1000016008, + * name: 'English', + * tag: 'en', + * originalName: 'English', + * flagEmoji: '🇬🇧', + * base: true + * }, + * { + * id: 1000016013, + * name: 'Spanish', + * tag: 'es', + * originalName: 'español', + * flagEmoji: '🇪🇸', + * base: false + * }, + * { + * id: 1000016009, + * name: 'Simplified Chinese', + * tag: 'zh-Hans', + * originalName: '简体中文', + * flagEmoji: '🇨🇳', + * base: false + * }, + * { + * id: 1000016012, + * name: 'Traditional Chinese', + * tag: 'zh-Hant', + * originalName: '繁體中文', + * flagEmoji: '🇭🇰', + * base: false + * } + * ] + * ``` + */ +export const getAllProjectLanguages = async (size = 1000) => { + const url = `/languages?size=${size}`; + const resp = await fetchTolgee(url); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json: { + _embedded: { + languages: { + id: number; + name: string; + tag: string; + originalName: string; + flagEmoji: string; + base: boolean; + }[]; + }; + page: unknown; + } = await resp.json(); + return json._embedded.languages; +}; + +/** + * Returns translations in project + * + * See https://tolgee.io/api#operation/getTranslations_ + */ +export const getTranslations = async () => { + const url = '/translations'; + const resp = await fetchTolgee(url); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json = await resp.json(); + return json; +}; + +/** + * Returns all translations for specified languages + * + * See https://tolgee.io/api#operation/getAllTranslations_1 + */ +export const getLanguagesTranslations = async ( + languages: T +) => { + const url = `/translations/${languages}`; + const resp = await fetchTolgee(url); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json: { [key in T]?: Record } = await resp.json(); + return json; +}; + +export const getRemoteTranslations = async (languages: string) => { + const translations = await getLanguagesTranslations(languages); + if (!(languages in translations)) { + return {}; + } + // The assert is safe because we checked above + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return translations[languages]!; +}; + +/** + * Creates new key + * + * See https://tolgee.io/api#operation/create_2 + */ +export const createsNewKey = async ( + key: string, + translations: Record +) => { + const url = '/translations/keys/create'; + const resp = await fetchTolgee(url, { + method: 'POST', + body: JSON.stringify({ name: key, translations }), + }); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json = await resp.json(); + return json; +}; + +/** + * Tags a key with tag. If tag with provided name doesn't exist, it is created + * + * See https://tolgee.io/api#operation/tagKey_1 + */ +export const addTag = async (keyId: string, tagName: string) => { + const url = `/keys/${keyId}/tags`; + const resp = await fetchTolgee(url, { + method: 'PUT', + body: JSON.stringify({ name: tagName }), + }); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json = await resp.json(); + return json; +}; + +/** + * Tags a key with tag. If tag with provided name doesn't exist, it is created + * + * See https://tolgee.io/api#operation/tagKey_1 + */ +export const removeTag = async (keyId: string, tagId: number) => { + const url = `/keys/${keyId}/tags/${tagId}`; + const resp = await fetchTolgee(url, { + method: 'DELETE', + }); + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + const json = await resp.json(); + return json; +}; + +// export const addTagByKey = async (key: string, tag: string) => { +// // TODO get key id by key name +// // const keyId = +// // addTag(keyId, tag); +// }; + +/** + * Exports data + * + * See https://tolgee.io/api#operation/export_1 + */ +export const exportResources = async () => { + const url = `/export`; + const resp = await fetchTolgee(url); + + if (resp.status < 200 || resp.status >= 300) { + throw new Error(url + ' ' + resp.status + '\n' + (await resp.text())); + } + return resp; +}; diff --git a/packages/app/src/libs/i18n/scripts/download.ts b/packages/app/src/libs/i18n/scripts/download.ts new file mode 100644 index 0000000000..a43e84bf22 --- /dev/null +++ b/packages/app/src/libs/i18n/scripts/download.ts @@ -0,0 +1,132 @@ +// cSpell:ignore Tolgee +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { format } from 'prettier'; +import { getAllProjectLanguages, getRemoteTranslations } from './api'; +import type { TranslationRes } from './utils'; + +const RES_DIR = path.resolve(process.cwd(), 'src', 'resources'); + +const countKeys = (obj: TranslationRes) => { + let count = 0; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + Object.entries(obj).forEach(([_, value]) => { + if (typeof value === 'string') { + count++; + } else { + count += countKeys(value); + } + }); + return count; +}; + +const getBaseTranslations = async (baseLanguage: { tag: string }) => { + try { + const baseTranslationsStr = await fs.readFile( + path.resolve(RES_DIR, `${baseLanguage.tag}.json`), + { encoding: 'utf8' } + ); + const baseTranslations = JSON.parse(baseTranslationsStr); + return baseTranslations; + } catch (e) { + console.error('base language:', JSON.stringify(baseLanguage)); + console.error('Failed to read base language', e); + const translations = await getRemoteTranslations(baseLanguage.tag); + await fs.writeFile( + path.resolve(RES_DIR, `${baseLanguage.tag}.json`), + JSON.stringify(translations, null, 4) + ); + } +}; + +const main = async () => { + console.log('Loading project languages...'); + const languages = await getAllProjectLanguages(); + const baseLanguage = languages.find(language => language.base); + if (!baseLanguage) { + console.error(JSON.stringify(languages)); + throw new Error('Could not find base language'); + } + console.log(`Loading ${baseLanguage.tag} languages translations as base...`); + + const baseTranslations = await getBaseTranslations(baseLanguage); + const baseKeyNum = countKeys(baseTranslations); + const languagesWithTranslations = await Promise.all( + languages.map(async language => { + console.log(`Loading ${language.tag} translations...`); + const translations = await getRemoteTranslations(language.tag); + const keyNum = countKeys(translations); + + return { + ...language, + translations, + completeRate: keyNum / baseKeyNum, + }; + }) + ); + + const availableLanguages = languagesWithTranslations.filter( + language => language.completeRate > 0 + ); + + availableLanguages + // skip base language + .filter(i => !i.base) + .forEach(async language => { + await fs.writeFile( + path.resolve(RES_DIR, `${language.tag}.json`), + JSON.stringify( + { + '// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.': + '', + ...language.translations, + }, + null, + 4 + ) + '\n' + ); + }); + + console.log('Generating meta data...'); + const code = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + // Run \`pnpm run download-resources\` to regenerate. + // To overwrite this, please overwrite ${path.basename(__filename)} + ${availableLanguages + .map( + language => + `import ${language.tag.replaceAll('-', '_')} from './${ + language.tag + }.json'` + ) + .join('\n')} + + export const LOCALES = [ + ${availableLanguages + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(({ translations, ...language }) => + JSON.stringify({ + ...language, + res: '__RES_PLACEHOLDER', + }).replace( + '"__RES_PLACEHOLDER"', + language.tag.replaceAll('-', '_') + ) + ) + .join(',\n')} + ] as const; + `; + + await fs.writeFile( + path.resolve(RES_DIR, 'index.ts'), + format(code, { + parser: 'typescript', + singleQuote: true, + trailingComma: 'es5', + tabWidth: 4, + arrowParens: 'avoid', + }) + ); + console.log('Done'); +}; + +main(); diff --git a/packages/app/src/libs/i18n/scripts/request.ts b/packages/app/src/libs/i18n/scripts/request.ts new file mode 100644 index 0000000000..717477d389 --- /dev/null +++ b/packages/app/src/libs/i18n/scripts/request.ts @@ -0,0 +1,55 @@ +// cSpell:ignore Tolgee +const TOLGEE_API_KEY = process.env['TOLGEE_API_KEY']; +const TOLGEE_API_URL = 'https://i18n.affine.pro'; + +if (!TOLGEE_API_KEY) { + throw new Error(`Please set "TOLGEE_API_KEY" as environment variable!`); +} + +const withTolgee = ( + fetch: typeof globalThis.fetch +): typeof globalThis.fetch => { + const baseUrl = `${TOLGEE_API_URL}/v2/projects`; + const headers = new Headers({ + 'X-API-Key': TOLGEE_API_KEY, + 'Content-Type': 'application/json', + }); + + const isRequest = (input: RequestInfo | URL): input is Request => { + return typeof input === 'object' && !('href' in input); + }; + + return new Proxy(fetch, { + apply( + target, + thisArg: unknown, + argArray: Parameters + ) { + if (isRequest(argArray[0])) { + // Request + if (!argArray[0].headers) { + argArray[0] = { + ...argArray[0], + url: `${baseUrl}${argArray[0].url}`, + headers, + }; + } + } else { + // URL or URLLike + ?RequestInit + if (typeof argArray[0] === 'string') { + argArray[0] = `${baseUrl}${argArray[0]}`; + } + if (!argArray[1]) { + argArray[1] = {}; + } + if (!argArray[1].headers) { + argArray[1].headers = headers; + } + } + // console.log('fetch', argArray); + return target.apply(thisArg, argArray); + }, + }); +}; + +export const fetchTolgee = withTolgee(globalThis.fetch); diff --git a/packages/app/src/libs/i18n/scripts/sync.ts b/packages/app/src/libs/i18n/scripts/sync.ts new file mode 100644 index 0000000000..80b16a5024 --- /dev/null +++ b/packages/app/src/libs/i18n/scripts/sync.ts @@ -0,0 +1,154 @@ +// cSpell:ignore Tolgee +import { readFile } from 'fs/promises'; +import path from 'path'; +import { createsNewKey, getRemoteTranslations } from './api'; +import type { TranslationRes } from './utils'; + +const BASE_JSON_PATH = path.resolve( + process.cwd(), + 'src', + 'resources', + 'en.json' +); +const BASE_LANGUAGES = 'en' as const; + +/** + * + * @example + * ```ts + * flatRes({ a: { b: 'c' } }); // { 'a.b': 'c' } + * ``` + */ +const flatRes = (obj: TranslationRes) => { + const getEntries = (o: TranslationRes, prefix = ''): [string, string][] => + Object.entries(o).flatMap<[string, string]>(([k, v]) => + typeof v !== 'string' + ? getEntries(v, `${prefix}${k}.`) + : [[`${prefix}${k}`, v]] + ); + return Object.fromEntries(getEntries(obj)); +}; + +const differenceObject = ( + newObj: Record, + oldObj: Record +) => { + const add: string[] = []; + const remove: string[] = []; + const modify: string[] = []; + const both: string[] = []; + + Object.keys(newObj).forEach(key => { + if (!(key in oldObj)) { + add.push(key); + } else { + both.push(key); + } + }); + + Object.keys(oldObj).forEach(key => { + if (!(key in newObj)) { + remove.push(key); + } + }); + + both.forEach(key => { + if (!(key in newObj) || !(key in oldObj)) { + throw new Error('Unreachable'); + } + const newVal = newObj[key]; + const oldVal = oldObj[key]; + if (newVal !== oldVal) { + modify.push(key); + } + }); + return { add, remove, modify }; +}; + +function warnDiff(diff: { add: string[]; remove: string[]; modify: string[] }) { + if (diff.add.length) { + console.log('New keys found:', diff.add.join(', ')); + //See https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-notice-message + process.env['CI'] && + console.log( + `::notice file=${BASE_JSON_PATH},line=1,title=New keys::${diff.add.join( + ', ' + )}` + ); + } + if (diff.remove.length) { + console.warn('[WARN]', 'Unused keys found:', diff.remove.join(', ')); + process.env['CI'] && + console.warn( + `::notice file=${BASE_JSON_PATH},line=1,title=Unused keys::${diff.remove.join( + ', ' + )}` + ); + } + if (diff.modify.length) { + console.warn('[WARN]', 'Inconsistent keys found:', diff.modify.join(', ')); + process.env['CI'] && + console.warn( + `::warning file=${BASE_JSON_PATH},line=1,title=Inconsistent keys::${diff.modify.join( + ', ' + )}` + ); + } +} + +const main = async () => { + console.log('Loading local base translations...'); + const baseLocalTranslations = JSON.parse( + await readFile(BASE_JSON_PATH, { + encoding: 'utf8', + }) + ); + const flatLocalTranslations = flatRes(baseLocalTranslations); + console.log( + `Loading local base translations success! Total ${ + Object.keys(flatLocalTranslations).length + } keys` + ); + + console.log('Fetch remote base translations...'); + const baseRemoteTranslations = await getRemoteTranslations(BASE_LANGUAGES); + const flatRemoteTranslations = flatRes(baseRemoteTranslations); + console.log( + `Fetch remote base translations success! Total ${ + Object.keys(flatRemoteTranslations).length + } keys` + ); + + const diff = differenceObject(flatLocalTranslations, flatRemoteTranslations); + + console.log(''); // new line + warnDiff(diff); + console.log(''); // new line + + if (process.argv.slice(2).includes('--check')) { + // check mode + return; + } + + diff.add.forEach(async key => { + const val = flatLocalTranslations[key]; + console.log(`Creating new key: ${key} -> ${val}`); + await createsNewKey(key, { [BASE_LANGUAGES]: val }); + }); + + // TODO remove unused tags from used keys + + // diff.remove.forEach(key => { + // // TODO set unused tag + // // console.log(`Add ${DEPRECATED_TAG_NAME} to ${key}`); + // addTagByKey(key, DEPRECATED_TAG_NAME); + // }); + + // diff.modify.forEach(key => { + // // TODO warn different between local and remote base translations + // }); + + // TODO send notification +}; + +main(); diff --git a/packages/app/src/libs/i18n/scripts/utils.ts b/packages/app/src/libs/i18n/scripts/utils.ts new file mode 100644 index 0000000000..c17e3446be --- /dev/null +++ b/packages/app/src/libs/i18n/scripts/utils.ts @@ -0,0 +1,3 @@ +export interface TranslationRes { + [x: string]: string | TranslationRes; +} diff --git a/packages/app/src/pages-content/ConfirmInvitationPage/AlreadyJoined.tsx b/packages/app/src/pages-content/ConfirmInvitationPage/AlreadyJoined.tsx new file mode 100644 index 0000000000..d3812e9451 --- /dev/null +++ b/packages/app/src/pages-content/ConfirmInvitationPage/AlreadyJoined.tsx @@ -0,0 +1,10 @@ +export const AlreadyJoined = () => { + return ( +
+

+ You are already a member of Workspace name +

+ +
+ ); +}; diff --git a/packages/app/src/pages-content/ConfirmInvitationPage/Confirm.tsx b/packages/app/src/pages-content/ConfirmInvitationPage/Confirm.tsx new file mode 100644 index 0000000000..b447026044 --- /dev/null +++ b/packages/app/src/pages-content/ConfirmInvitationPage/Confirm.tsx @@ -0,0 +1,11 @@ +export const Confirm = () => { + return ( +
+

Inviter name

+

+ invite you to join in Workspace name +

+ +
+ ); +}; diff --git a/packages/app/src/pages-content/ConfirmInvitationPage/LinkExpired.tsx b/packages/app/src/pages-content/ConfirmInvitationPage/LinkExpired.tsx new file mode 100644 index 0000000000..8bdd4e6d54 --- /dev/null +++ b/packages/app/src/pages-content/ConfirmInvitationPage/LinkExpired.tsx @@ -0,0 +1,8 @@ +export const LinkExpired = () => { + return ( +
+

The current invitation link has expired.

+ Back to home +
+ ); +}; diff --git a/packages/app/src/pages-content/ConfirmInvitationPage/index.tsx b/packages/app/src/pages-content/ConfirmInvitationPage/index.tsx new file mode 100644 index 0000000000..2bf8010c70 --- /dev/null +++ b/packages/app/src/pages-content/ConfirmInvitationPage/index.tsx @@ -0,0 +1,21 @@ +import { useRouter } from 'next/router'; +import { AlreadyJoined } from './AlreadyJoined'; +import { Confirm } from './Confirm'; +import { LinkExpired } from './LinkExpired'; + +export const ConfirmInvitation = () => { + const router = useRouter(); + // Temporary code. The code should be returned by request. + const { code } = router.query; + const Component = { + '-1': LinkExpired, + 0: Confirm, + 1: AlreadyJoined, + }[code as string]; + return ( +
+

Confirm Invitation

+ {Component ? : null} +
+ ); +}; diff --git a/packages/app/src/pages/404.tsx b/packages/app/src/pages/404.tsx new file mode 100644 index 0000000000..87ab0471be --- /dev/null +++ b/packages/app/src/pages/404.tsx @@ -0,0 +1,4 @@ +import NotfoundPage from '@/components/404'; +export default function Custom404() { + return ; +} diff --git a/packages/app/src/pages/_app.tsx b/packages/app/src/pages/_app.tsx index afa0fb03e7..9446954124 100644 --- a/packages/app/src/pages/_app.tsx +++ b/packages/app/src/pages/_app.tsx @@ -3,31 +3,67 @@ import dynamic from 'next/dynamic'; import '../../public/globals.css'; import '../../public/variable.css'; import './temporary.css'; -import { EditorProvider } from '@/components/editor-provider'; -import { ModalProvider } from '@/components/global-modal-provider'; import { Logger } from '@toeverything/pathfinder-logger'; - import '@fontsource/space-mono'; import '@fontsource/poppins'; import '../utils/print-build-info'; - -const ThemeProvider = dynamic(() => import('@/styles/themeProvider'), { +import ProviderComposer from '@/components/provider-composer'; +import type { PropsWithChildren, ReactElement, ReactNode } from 'react'; +import type { NextPage } from 'next'; +import { AppStateProvider } from '@/providers/app-state-provider/provider'; +import ConfirmProvider from '@/providers/confirm-provider'; +import { ModalProvider } from '@/providers/global-modal-provider'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; +import { useAppState } from '@/providers/app-state-provider'; +import { PageLoading } from '@/components/loading'; +const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), { ssr: false, }); -function MyApp({ Component, pageProps }: AppProps) { +export type NextPageWithLayout

, IP = P> = NextPage< + P, + IP +> & { + getLayout?: (page: ReactElement) => ReactNode; +}; + +type AppPropsWithLayout = AppProps & { + Component: NextPageWithLayout; +}; + +const App = ({ Component, pageProps }: AppPropsWithLayout) => { + const getLayout = Component.getLayout || (page => page); + return ( <> - - - - - - - + , + , + , + , + ]} + > + {getLayout()} + ); -} +}; -export default MyApp; +const AppDefender = ({ children }: PropsWithChildren) => { + const router = useRouter(); + + const { synced } = useAppState(); + + useEffect(() => { + if (router.pathname === '/') { + router.replace('/workspace'); + } + }, [router]); + + return

{synced ? children : }
; +}; + +export default App; diff --git a/packages/app/src/pages/affine.tsx b/packages/app/src/pages/affine.tsx index a0c3ee1cd1..fa994e85cc 100644 --- a/packages/app/src/pages/affine.tsx +++ b/packages/app/src/pages/affine.tsx @@ -1,8 +1,10 @@ import { displayFlex, styled } from '@/styles'; -import { ThemeModeSwitch } from '@/components/theme-mode-switch'; -import { Loading } from '@/components/loading'; +import Loading from '@/components/loading'; import Modal from '@/ui/modal'; import { useState } from 'react'; +import { Button } from '@/ui/button'; +import { FavouritedIcon } from '@blocksuite/icons'; +import { toast } from '@/ui/toast'; export const StyledHeader = styled('div')({ height: '60px', width: '100vw', @@ -17,7 +19,6 @@ const Affine = () => { return ( <> - + + + + + + + + ); }; diff --git a/packages/app/src/pages/affine/[workspace_id].tsx b/packages/app/src/pages/affine/[workspace_id].tsx new file mode 100644 index 0000000000..875928acf9 --- /dev/null +++ b/packages/app/src/pages/affine/[workspace_id].tsx @@ -0,0 +1,20 @@ +// for dynamic route get workspace id maybe path will change +import { useRouter } from 'next/router'; + +const Post = () => { + const router = useRouter(); + const { workspace_id } = router.query; + + return ( +

+ workspace_id: {workspace_id}, +

+ ); +}; + +export default Post; diff --git a/packages/app/src/pages/confirm-invitation.tsx b/packages/app/src/pages/confirm-invitation.tsx new file mode 100644 index 0000000000..ce3b436745 --- /dev/null +++ b/packages/app/src/pages/confirm-invitation.tsx @@ -0,0 +1,8 @@ +import type { NextPage } from 'next'; +import { ConfirmInvitation } from '../pages-content/ConfirmInvitationPage'; + +const ConfirmInvitationPage: NextPage = () => { + return ; +}; + +export default ConfirmInvitationPage; diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx index 8328838309..ae21815c9c 100644 --- a/packages/app/src/pages/index.tsx +++ b/packages/app/src/pages/index.tsx @@ -1,66 +1,7 @@ import type { NextPage } from 'next'; -import dynamic from 'next/dynamic'; -import { styled } from '@/styles'; -import { Header } from '@/components/Header'; -import { FAQ } from '@/components/faq'; -import Loading from '@/components/loading'; -import EdgelessToolbar from '@/components/edgeless-toolbar'; -import MobileModal from '@/components/mobile-modal'; -import '@/components/simple-counter'; - -const StyledEditorContainer = styled('div')(({ theme }) => { - return { - height: 'calc(100vh - 60px)', - }; -}); - -const StyledPage = styled('div')(({ theme }) => { - return { - height: '100vh', - backgroundColor: theme.colors.pageBackground, - transition: 'background-color .5s', - }; -}); - -const StyledLoadingContainer = styled('div')(({ theme }) => { - return { - height: '100vh', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - color: theme.colors.primaryColor, - h1: { - fontSize: '2em', - marginTop: '150px', - fontWeight: '600', - }, - }; -}); - -const DynamicEditor = dynamic(() => import('../components/editor'), { - loading: () => ( - -
- -

Loading...

-
-
- ), - ssr: false, -}); const Home: NextPage = () => { - return ( - -
- - - - - - - - ); + return
Home Page
; }; export default Home; diff --git a/packages/app/src/pages/invite/[invite_code].tsx b/packages/app/src/pages/invite/[invite_code].tsx new file mode 100644 index 0000000000..b0f8fece67 --- /dev/null +++ b/packages/app/src/pages/invite/[invite_code].tsx @@ -0,0 +1,144 @@ +import { styled } from '@/styles'; +import { Empty } from '@/ui/empty'; +import { Avatar } from '@mui/material'; +import { acceptInviting } from '@affine/data-services'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; + +const User = ({ name, avatar }: { name: string; avatar?: string }) => { + return ( + + {avatar ? ( + + ) : ( + {name.slice(0, 1)} + )} + {name} + + ); +}; + +export default function DevPage() { + const router = useRouter(); + const [successInvited, setSuccessInvited] = useState(false); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const [inviteData, setInviteData] = useState(null); + useEffect(() => { + acceptInviting({ invitingCode: router.query.invite_code as string }) + .then(data => { + setSuccessInvited(true); + setInviteData(data); + }) + .catch(err => { + console.log('err: ', err); + }); + }, [router.query.invite_code]); + + return ( + +
+ + + + invited + you to join + + {successInvited ? ( + + + + + + Successfully joined + + ) : ( + + + + + + The link has expired + + )} + +
+
+ ); +} +const UserIcon = styled('div')({ + display: 'inline-block', + width: '28px', + height: '28px', + borderRadius: '50%', + backgroundColor: '#FFF5AB', + textAlign: 'center', + color: '#896406', + lineHeight: '28px', +}); + +const Invited = styled('div')(({ theme }) => { + return { + height: '100vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + textAlign: 'center', + color: theme.colors.textColor, + backgroundColor: theme.colors.pageBackground, + }; +}); + +const Content = styled('div')({ + fontSize: '16px', + marginTop: '35px', +}); + +const UserContent = styled('span')({ + fontSize: '18px', + marginLeft: '12px', + span: { + padding: '0 12px', + }, +}); + +const Status = styled('div')(() => { + return { + marginTop: '16px', + svg: { + verticalAlign: 'middle', + marginRight: '12px', + }, + }; +}); diff --git a/packages/app/src/pages/playground/templates.tsx b/packages/app/src/pages/playground/templates.tsx new file mode 100644 index 0000000000..8d03b111f9 --- /dev/null +++ b/packages/app/src/pages/playground/templates.tsx @@ -0,0 +1,119 @@ +import { ReactElement } from 'react'; +import WorkspaceLayout from '@/components/workspace-layout'; +import exampleMarkdown1 from '@/templates/Welcome-to-the-AFFiNE-Alpha.md'; +import exampleMarkdown2 from '@/templates/AFFiNE-Docs.md'; + +import { usePageHelper } from '@/hooks/use-page-helper'; +import { useAppState } from '@/providers/app-state-provider/context'; +import { Button } from '@/ui/button'; +interface Template { + name: string; + source: string; +} +const TemplateItemContainer = styled('div')(() => { + return { + color: 'blue', + padding: '10px 15px', + borderBottom: '1px solid #eee', + cursor: 'pointer', + '&:hover': { + background: '#eee', + }, + }; +}); +import { styled } from '@/styles'; +const TEMPLATES: Template[] = [ + { + name: 'Welcome-to-the-AFFiNE-Alpha.md', + source: exampleMarkdown1, + }, + { + name: 'AFFiNE-Docs.md', + source: exampleMarkdown2, + }, +]; + +const All = () => { + const { openPage, createPage } = usePageHelper(); + const { currentWorkspace } = useAppState(); + const _applyTemplate = function (pageId: string, template: Template) { + const page = currentWorkspace?.getPage(pageId); + + const title = template.name; + if (page) { + currentWorkspace?.setPageMeta(page.id, { title }); + if (page && page.root === null) { + setTimeout(() => { + const editor = document.querySelector('editor-container'); + if (editor) { + const groupId = page.addBlock({ flavour: 'affine:group' }, pageId); + // TODO blocksuite should offer a method to import markdown from store + editor.clipboard.importMarkdown(template.source, `${groupId}`); + page.resetHistory(); + editor.requestUpdate(); + } + }, 300); + } + } + }; + const _handleAppleTemplate = async function (template: Template) { + const pageId = await createPage(); + if (pageId) { + openPage(pageId); + _applyTemplate(pageId, template); + } + }; + const _handleAppleTemplateFromRemoteUrl = async () => { + if (!window.showOpenFilePicker) { + return; + } + const arrFileHandle = await window.showOpenFilePicker({ + types: [ + { + accept: { + 'image/*': ['.md'], + }, + }, + ], + multiple: false, + }); + for (const fileHandle of arrFileHandle) { + const file = await fileHandle.getFile(); + const text = await file.text(); + _handleAppleTemplate({ + name: file.name, + source: text, + }); + } + }; + + return ( +
+
+

Templates

+ {TEMPLATES.map(template => { + return ( + _handleAppleTemplate(template)} + > + {template.name} + + + ); + })} +
+

Import Markdown

+ +
+
+ ); +}; + +All.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default All; diff --git a/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx b/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx new file mode 100644 index 0000000000..726a23b677 --- /dev/null +++ b/packages/app/src/pages/workspace/[workspaceId]/[pageId].tsx @@ -0,0 +1,116 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { + useRef, + useEffect, + useState, + ReactElement, + PropsWithChildren, +} from 'react'; +import { styled } from '@/styles'; +import { EditorHeader } from '@/components/header'; +import EdgelessToolbar from '@/components/edgeless-toolbar'; +import MobileModal from '@/components/mobile-modal'; +import { useAppState } from '@/providers/app-state-provider/context'; +import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-v2.0.md'; +import type { NextPageWithLayout } from '../..//_app'; +import WorkspaceLayout from '@/components/workspace-layout'; +import { useRouter } from 'next/router'; +import { usePageHelper } from '@/hooks/use-page-helper'; +const StyledEditorContainer = styled('div')(() => { + return { + height: 'calc(100vh - 60px)', + }; +}); + +const Page: NextPageWithLayout = () => { + const editorContainer = useRef(null); + const { createEditor, setEditor, currentPage, currentWorkspace } = + useAppState(); + + useEffect(() => { + const ret = () => { + const node = editorContainer.current; + while (node?.firstChild) { + node.removeChild(node.firstChild); + } + }; + + const editor = createEditor?.current?.(currentPage!); + if (editor) { + editorContainer.current?.appendChild(editor); + setEditor?.current?.(editor); + if (currentPage!.isEmpty) { + const isFirstPage = currentWorkspace?.meta.pageMetas.length === 1; + // Can not use useCurrentPageMeta to get new title, cause meta title will trigger rerender, but the second time can not remove title + const { title: metaTitle } = currentPage!.meta; + const title = metaTitle + ? metaTitle + : isFirstPage + ? 'Welcome to AFFiNE Alpha "Abbey Wood"' + : ''; + currentWorkspace?.setPageMeta(currentPage!.id, { title }); + + const pageId = currentPage!.addBlock({ + flavour: 'affine:page', + title, + }); + const groupId = currentPage!.addBlock( + { flavour: 'affine:group' }, + pageId + ); + currentPage!.addBlock({ flavour: 'affine:group' }, pageId); + // If this is a first page in workspace, init an introduction markdown + if (isFirstPage) { + editor.clipboard.importMarkdown(exampleMarkdown, `${groupId}`); + currentWorkspace!.setPageMeta(currentPage!.id, { title }); + } + currentPage!.resetHistory(); + } + } + + return ret; + }, [currentWorkspace, currentPage, createEditor, setEditor]); + + return ( + <> + + + + + + ); +}; + +const PageDefender = ({ children }: PropsWithChildren) => { + const router = useRouter(); + const [pageLoaded, setPageLoaded] = useState(false); + const { currentWorkspace, loadPage } = useAppState(); + const { createPage } = usePageHelper(); + + useEffect(() => { + const initPage = async () => { + const pageId = router.query.pageId as string; + const isPageExist = !!currentWorkspace!.meta.pageMetas.find( + p => p.id === pageId + ); + if (!isPageExist) { + await createPage({ pageId }); + } + await loadPage(pageId); + setPageLoaded(true); + }; + initPage(); + }, [createPage, currentWorkspace, loadPage, router.query.pageId]); + + return <>{pageLoaded ? children : null}; +}; + +Page.getLayout = function getLayout(page: ReactElement) { + return ( + + {page} + + ); +}; + +export default Page; diff --git a/packages/app/src/pages/workspace/[workspaceId]/all.tsx b/packages/app/src/pages/workspace/[workspaceId]/all.tsx new file mode 100644 index 0000000000..dacb16bd59 --- /dev/null +++ b/packages/app/src/pages/workspace/[workspaceId]/all.tsx @@ -0,0 +1,26 @@ +import { PageList } from '@/components/page-list'; +import { AllPagesIcon } from '@blocksuite/icons'; +import usePageMetaList from '@/hooks/use-page-meta-list'; +import { PageListHeader } from '@/components/header'; +import { ReactElement } from 'react'; +import WorkspaceLayout from '@/components/workspace-layout'; + +const All = () => { + const pageMetaList = usePageMetaList(); + + return ( + <> + }>All Page + !p.trash)} + showFavoriteTag={true} + /> + + ); +}; + +All.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default All; diff --git a/packages/app/src/pages/workspace/[workspaceId]/favorite.tsx b/packages/app/src/pages/workspace/[workspaceId]/favorite.tsx new file mode 100644 index 0000000000..bde04923d6 --- /dev/null +++ b/packages/app/src/pages/workspace/[workspaceId]/favorite.tsx @@ -0,0 +1,20 @@ +import { PageListHeader } from '@/components/header'; +import { PageList } from '@/components/page-list'; +import { FavouritesIcon } from '@blocksuite/icons'; +import usePageMetaList from '@/hooks/use-page-meta-list'; +import { ReactElement } from 'react'; +import WorkspaceLayout from '@/components/workspace-layout'; + +export const Favorite = () => { + const pageMetaList = usePageMetaList(); + return ( + <> + }>Favourites + p.favorite && !p.trash)} /> + + ); +}; +Favorite.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; +export default Favorite; diff --git a/packages/app/src/pages/workspace/[workspaceId]/index.tsx b/packages/app/src/pages/workspace/[workspaceId]/index.tsx new file mode 100644 index 0000000000..39a1ad121b --- /dev/null +++ b/packages/app/src/pages/workspace/[workspaceId]/index.tsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider/context'; +import useEnsureWorkspace from '@/hooks/use-ensure-workspace'; +import { PageLoading } from '@/components/loading'; +import usePageHelper from '@/hooks/use-page-helper'; + +const WorkspaceIndex = () => { + const router = useRouter(); + const { currentWorkspaceId, currentWorkspace } = useAppState(); + const { createPage } = usePageHelper(); + const { workspaceLoaded } = useEnsureWorkspace(); + + useEffect(() => { + const initPage = async () => { + if (!workspaceLoaded) { + return; + } + const savedPageId = currentWorkspace?.meta.pageMetas[0]?.id; + if (savedPageId) { + router.replace(`/workspace/${currentWorkspaceId}/${savedPageId}`); + return; + } + + const pageId = await createPage(); + router.replace(`/workspace/${currentWorkspaceId}/${pageId}`); + }; + initPage(); + }, [ + currentWorkspace, + currentWorkspaceId, + createPage, + router, + workspaceLoaded, + ]); + + return ; +}; + +export default WorkspaceIndex; diff --git a/packages/app/src/pages/workspace/[workspaceId]/trash.tsx b/packages/app/src/pages/workspace/[workspaceId]/trash.tsx new file mode 100644 index 0000000000..9e7f397669 --- /dev/null +++ b/packages/app/src/pages/workspace/[workspaceId]/trash.tsx @@ -0,0 +1,22 @@ +import { PageListHeader } from '@/components/header'; +import { PageList } from '@/components/page-list'; +import { TrashIcon } from '@blocksuite/icons'; +import usePageMetaList from '@/hooks/use-page-meta-list'; +import { ReactElement } from 'react'; +import WorkspaceLayout from '@/components/workspace-layout'; + +export const Trash = () => { + const pageMetaList = usePageMetaList(); + return ( + <> + }>Trash + p.trash)} isTrash={true} /> + + ); +}; + +Trash.getLayout = function getLayout(page: ReactElement) { + return {page}; +}; + +export default Trash; diff --git a/packages/app/src/pages/workspace/index.tsx b/packages/app/src/pages/workspace/index.tsx new file mode 100644 index 0000000000..1fe3ba2c17 --- /dev/null +++ b/packages/app/src/pages/workspace/index.tsx @@ -0,0 +1,21 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; +import { useAppState } from '@/providers/app-state-provider'; +import useEnsureWorkspace from '@/hooks/use-ensure-workspace'; +import { PageLoading } from '@/components/loading'; + +export const WorkspaceIndex = () => { + const router = useRouter(); + const { currentWorkspaceId } = useAppState(); + const { workspaceLoaded } = useEnsureWorkspace(); + + useEffect(() => { + if (workspaceLoaded) { + router.push(`/workspace/${currentWorkspaceId}`); + } + }, [currentWorkspaceId, router, workspaceLoaded]); + + return ; +}; + +export default WorkspaceIndex; diff --git a/packages/app/src/providers/app-state-provider/context.ts b/packages/app/src/providers/app-state-provider/context.ts new file mode 100644 index 0000000000..5dda7ddbd6 --- /dev/null +++ b/packages/app/src/providers/app-state-provider/context.ts @@ -0,0 +1,67 @@ +import { createContext, MutableRefObject, useContext } from 'react'; +import type { Workspace } from '@affine/data-services'; +import { AccessTokenMessage } from '@affine/data-services'; +import type { + Page as StorePage, + Workspace as StoreWorkspace, +} from '@blocksuite/store'; +import type { EditorContainer } from '@blocksuite/editor'; +export type LoadWorkspaceHandler = ( + workspaceId: string, + websocket?: boolean, + user?: AccessTokenMessage | null +) => Promise | null; +export type CreateEditorHandler = (page: StorePage) => EditorContainer | null; + +export interface AppStateValue { + user: AccessTokenMessage | null; + workspacesMeta: Workspace[]; + + currentWorkspaceId: string; + currentWorkspace: StoreWorkspace | null; + + currentPage: StorePage | null; + + workspaces: Record; + + editor: EditorContainer | null; + synced: boolean; + refreshWorkspacesMeta: () => void; +} + +export interface AppStateContext extends AppStateValue { + setState: (state: AppStateValue) => void; + createEditor?: MutableRefObject< + ((page: StorePage) => EditorContainer | null) | undefined + >; + setEditor?: MutableRefObject<((page: EditorContainer) => void) | undefined>; + loadWorkspace: (workspaceId: string) => Promise; + loadPage: (pageId: string) => Promise; +} + +export const AppState = createContext({ + user: null, + workspacesMeta: [], + + currentWorkspaceId: '', + currentWorkspace: null, + + currentPage: null, + + editor: null, + + // eslint-disable-next-line @typescript-eslint/no-empty-function + setState: () => {}, + createEditor: undefined, + setEditor: undefined, + loadWorkspace: () => Promise.resolve(null), + loadPage: () => Promise.resolve(null), + synced: false, + // eslint-disable-next-line @typescript-eslint/no-empty-function + refreshWorkspacesMeta: () => {}, + workspaces: {}, +}); + +export const useAppState = () => { + return useContext(AppState); +}; diff --git a/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx new file mode 100644 index 0000000000..d30e0e02a1 --- /dev/null +++ b/packages/app/src/providers/app-state-provider/dynamic-blocksuite.tsx @@ -0,0 +1,99 @@ +import { useEffect } from 'react'; +import type { Page } from '@blocksuite/store'; +import { + IndexedDBDocProvider, + Workspace as StoreWorkspace, +} from '@blocksuite/store'; +import '@blocksuite/blocks'; +import { EditorContainer } from '@blocksuite/editor'; +import { BlockSchema } from '@blocksuite/blocks/models'; +import type { LoadWorkspaceHandler, CreateEditorHandler } from './context'; + +interface Props { + setLoadWorkspaceHandler: (handler: LoadWorkspaceHandler) => void; + setCreateEditorHandler: (handler: CreateEditorHandler) => void; +} + +const DynamicBlocksuite = ({ + setLoadWorkspaceHandler, + setCreateEditorHandler, +}: Props) => { + useEffect(() => { + const openWorkspace: LoadWorkspaceHandler = ( + workspaceId: string, + websocket = false, + user + ) => + // eslint-disable-next-line no-async-promise-executor + new Promise(async resolve => { + const workspace = new StoreWorkspace({ + room: workspaceId, + providers: [IndexedDBDocProvider], + }).register(BlockSchema); + console.log('websocket', websocket); + console.log('user', user); + + // if (websocket && token.refresh) { + // // FIXME: if add websocket provider, the first page will be blank + // const ws = new WebsocketProvider( + // `ws${window.location.protocol === 'https:' ? 's' : ''}://${ + // window.location.host + // }/api/sync/`, + // workspaceId, + // workspace.doc, + // { + // params: { + // token: token.refresh, + // }, + // awareness: workspace.meta.awareness.awareness, + // } + // ); + // + // ws.shouldConnect = false; + // + // // FIXME: there needs some method to destroy websocket. + // // Or we need a manager to manage websocket. + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-expect-error + // workspace.__ws__ = ws; + // } + + const indexDBProvider = workspace.providers.find( + p => p instanceof IndexedDBDocProvider + ); + // if (user) { + // const updates = await downloadWorkspace({ workspaceId }); + // updates && + // StoreWorkspace.Y.applyUpdate( + // workspace.doc, + // new Uint8Array(updates) + // ); + // // if after update, the space:meta is empty, then we need to get map with doc + // workspace.doc.getMap('space:meta'); + // } + if (indexDBProvider) { + (indexDBProvider as IndexedDBDocProvider).whenSynced.then(() => { + resolve(workspace); + }); + } else { + resolve(workspace); + } + }); + + setLoadWorkspaceHandler(openWorkspace); + }, [setLoadWorkspaceHandler]); + + useEffect(() => { + const createEditorHandler: CreateEditorHandler = (page: Page) => { + const editor = new EditorContainer(); + editor.page = page; + return editor; + }; + + setCreateEditorHandler(createEditorHandler); + }, [setCreateEditorHandler]); + + return <>; +}; + +export default DynamicBlocksuite; diff --git a/packages/app/src/providers/app-state-provider/hooks.ts b/packages/app/src/providers/app-state-provider/hooks.ts new file mode 100644 index 0000000000..6996bd03ef --- /dev/null +++ b/packages/app/src/providers/app-state-provider/hooks.ts @@ -0,0 +1,51 @@ +import { useEffect } from 'react'; +import { useRouter } from 'next/router'; +import { useAppState } from './context'; +import { usePageHelper } from '@/hooks/use-page-helper'; +export const useLoadWorkspace = () => { + const router = useRouter(); + const { loadWorkspace, currentWorkspace, currentWorkspaceId } = useAppState(); + + const workspaceId = router.query.workspaceId as string; + + useEffect(() => { + loadWorkspace?.(workspaceId); + }, [workspaceId, loadWorkspace]); + + return currentWorkspaceId === workspaceId ? currentWorkspace : null; +}; + +export const useLoadPage = () => { + const router = useRouter(); + const { loadPage, currentPage, currentWorkspaceId } = useAppState(); + const { createPage } = usePageHelper(); + const workspace = useLoadWorkspace(); + + const pageId = router.query.pageId as string; + + useEffect(() => { + if (!workspace) { + return; + } + const page = pageId ? workspace?.getPage(pageId) : null; + if (page) { + loadPage?.(pageId); + return; + } + + const savedPageId = workspace.meta.pageMetas[0]?.id; + if (savedPageId) { + router.push(`/workspace/${currentWorkspaceId}/${savedPageId}`); + return; + } + + createPage().then(async pageId => { + if (!pageId) { + return; + } + router.push(`/workspace/${currentWorkspaceId}/${pageId}`); + }); + }, [workspace, pageId, loadPage, createPage, router, currentWorkspaceId]); + + return currentPage?.id === pageId ? currentPage : null; +}; diff --git a/packages/app/src/providers/app-state-provider/hooks/use-sync-data.ts b/packages/app/src/providers/app-state-provider/hooks/use-sync-data.ts new file mode 100644 index 0000000000..ae1d37fe7e --- /dev/null +++ b/packages/app/src/providers/app-state-provider/hooks/use-sync-data.ts @@ -0,0 +1,48 @@ +import { useEffect } from 'react'; +import { + AccessTokenMessage, + getWorkspaces, + token, +} from '@affine/data-services'; +import { LoadWorkspaceHandler } from '../context'; + +export const useSyncData = ({ + loadWorkspaceHandler, +}: { + loadWorkspaceHandler: LoadWorkspaceHandler; +}) => { + useEffect(() => { + if (!loadWorkspaceHandler) { + return; + } + const start = async () => { + const isLogin = await token.refreshToken().catch(() => false); + return isLogin; + }; + start(); + + const callback = async (user: AccessTokenMessage | null) => { + const workspacesMeta = user + ? await getWorkspaces().catch(() => { + return []; + }) + : []; + // setState(state => ({ + // ...state, + // user: user, + // workspacesMeta, + // synced: true, + // })); + return workspacesMeta; + }; + + token.onChange(callback); + token.refreshToken().catch(err => { + // FIXME: should resolve invalid refresh token + console.log(err); + }); + return () => { + token.offChange(callback); + }; + }, [loadWorkspaceHandler]); +}; diff --git a/packages/app/src/providers/app-state-provider/index.ts b/packages/app/src/providers/app-state-provider/index.ts new file mode 100644 index 0000000000..88c38fd7cf --- /dev/null +++ b/packages/app/src/providers/app-state-provider/index.ts @@ -0,0 +1,2 @@ +export * from './context'; +export * from './interface'; diff --git a/packages/app/src/providers/app-state-provider/interface.ts b/packages/app/src/providers/app-state-provider/interface.ts new file mode 100644 index 0000000000..19b028703d --- /dev/null +++ b/packages/app/src/providers/app-state-provider/interface.ts @@ -0,0 +1,17 @@ +import { PageMeta as OriginalPageMeta } from '@blocksuite/store/dist/workspace/workspace'; + +// export type PageMeta = { +// favorite: boolean; +// trash: boolean; +// trashDate: number | void; +// updatedDate: number | void; +// mode: EditorContainer['mode']; +// } & OriginalPageMeta; + +export interface PageMeta extends OriginalPageMeta { + favorite: boolean; + trash: boolean; + trashDate: number; + updatedDate: number; + mode: 'edgeless' | 'page'; +} diff --git a/packages/app/src/providers/app-state-provider/provider.tsx b/packages/app/src/providers/app-state-provider/provider.tsx new file mode 100644 index 0000000000..44971a002a --- /dev/null +++ b/packages/app/src/providers/app-state-provider/provider.tsx @@ -0,0 +1,181 @@ +import { useMemo, useState, useEffect, useCallback, useRef } from 'react'; +import type { ReactNode } from 'react'; +import dynamic from 'next/dynamic'; +import { getWorkspaces } from '@affine/data-services'; +import { AppState, AppStateContext } from './context'; +import type { + AppStateValue, + CreateEditorHandler, + LoadWorkspaceHandler, +} from './context'; +import { Page, Workspace as StoreWorkspace } from '@blocksuite/store'; +import { EditorContainer } from '@blocksuite/editor'; +const DynamicBlocksuite = dynamic(() => import('./dynamic-blocksuite'), { + ssr: false, +}); + +export const AppStateProvider = ({ children }: { children?: ReactNode }) => { + const refreshWorkspacesMeta = async () => { + const workspacesMeta = await getWorkspaces().catch(() => { + return []; + }); + setState(state => ({ ...state, workspacesMeta })); + }; + + const [state, setState] = useState({ + user: null, + workspacesMeta: [], + currentWorkspaceId: '', + currentWorkspace: null, + currentPage: null, + editor: null, + // Synced is used to ensure that the provider has synced with the server, + // So after Synced set to true, the other state is sure to be set. + synced: false, + refreshWorkspacesMeta, + workspaces: {}, + }); + + useEffect(() => { + (async () => { + const workspacesList = await Promise.all( + state.workspacesMeta.map(async ({ id }) => { + const workspace = + (await loadWorkspaceHandler?.(id, false, state.user)) || null; + return { id, workspace }; + }) + ); + const workspaces: Record = {}; + workspacesList.forEach(({ id, workspace }) => { + workspaces[id] = workspace; + }); + setState(state => ({ + ...state, + workspaces, + })); + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [state.workspacesMeta]); + + const [loadWorkspaceHandler, _setLoadWorkspaceHandler] = + useState(); + const setLoadWorkspaceHandler = useCallback( + (handler: LoadWorkspaceHandler) => { + _setLoadWorkspaceHandler(() => handler); + }, + [_setLoadWorkspaceHandler] + ); + + const [createEditorHandler, _setCreateEditorHandler] = + useState(); + + const setCreateEditorHandler = useCallback( + (handler: CreateEditorHandler) => { + _setCreateEditorHandler(() => handler); + }, + [_setCreateEditorHandler] + ); + + const loadWorkspace = useRef(() => + Promise.resolve(null) + ); + loadWorkspace.current = async (workspaceId: string) => { + if (state.currentWorkspaceId === workspaceId) { + return state.currentWorkspace; + } + const workspace = + (await loadWorkspaceHandler?.(workspaceId, true, state.user)) || null; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + window.workspace = workspace; + // FIXME: there needs some method to destroy websocket. + // Or we need a manager to manage websocket. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + state.currentWorkspace?.__ws__?.destroy(); + + setState(state => ({ + ...state, + currentWorkspace: workspace, + currentWorkspaceId: workspaceId, + })); + return workspace; + }; + const loadPage = useRef(() => + Promise.resolve(null) + ); + loadPage.current = async (pageId: string) => { + const { currentWorkspace, currentPage } = state; + if (pageId === currentPage?.id) { + return currentPage; + } + const page = (pageId ? currentWorkspace?.getPage(pageId) : null) || null; + setState(state => ({ ...state, currentPage: page })); + return page; + }; + + const createEditor = useRef< + ((page: Page) => EditorContainer | null) | undefined + >(); + createEditor.current = () => { + const { currentPage, currentWorkspace } = state; + if (!currentPage || !currentWorkspace) { + return null; + } + const editor = createEditorHandler?.(currentPage) || null; + + if (editor) { + const pageMeta = currentWorkspace.meta.pageMetas.find( + p => p.id === currentPage.id + ); + if (pageMeta?.mode) { + editor.mode = pageMeta.mode as 'page' | 'edgeless' | undefined; + } + if (pageMeta?.trash) { + editor.readonly = true; + } + } + + return editor; + }; + + const setEditor = useRef<(editor: AppStateValue['editor']) => void>(); + + setEditor.current = (editor: AppStateValue['editor']) => { + setState(state => ({ ...state, editor })); + }; + + useEffect(() => { + if (!loadWorkspaceHandler) { + return; + } + setState(state => ({ + ...state, + workspacesMeta: [], + synced: true, + })); + }, [loadWorkspaceHandler]); + + const context = useMemo( + () => ({ + ...state, + setState, + createEditor, + setEditor, + loadWorkspace: loadWorkspace.current, + loadPage: loadPage.current, + }), + [state, setState, loadPage, loadWorkspace] + ); + + return ( + + + {children} + + ); +}; diff --git a/packages/app/src/providers/confirm-provider.tsx b/packages/app/src/providers/confirm-provider.tsx new file mode 100644 index 0000000000..cc349f3c60 --- /dev/null +++ b/packages/app/src/providers/confirm-provider.tsx @@ -0,0 +1,64 @@ +import { createContext, useContext, useState, ReactNode } from 'react'; +import type { PropsWithChildren } from 'react'; +import { Confirm, ConfirmProps } from '@/ui/confirm'; + +type ConfirmContextValue = { + confirm: (props: ConfirmProps) => Promise; +}; +type ConfirmContextProps = PropsWithChildren>; + +export const ConfirmContext = createContext({ + confirm: () => Promise.resolve(false), +}); + +export const useConfirm = () => useContext(ConfirmContext); + +export const ConfirmProvider = ({ + children, +}: PropsWithChildren) => { + const [confirmRecord, setConfirmRecord] = useState>( + {} + ); + return ( + { + return new Promise(resolve => { + const confirmId = String(Date.now()); + const closeHandler = () => { + delete confirmRecord[confirmId]; + setConfirmRecord({ ...confirmRecord }); + }; + setConfirmRecord(oldConfirmRecord => { + return { + ...oldConfirmRecord, + [confirmId]: ( + { + closeHandler(); + onCancel?.(); + resolve(false); + }} + onConfirm={() => { + closeHandler(); + onConfirm?.(); + resolve(true); + }} + /> + ), + }; + }); + }); + }, + }} + > + {children} + {Object.entries(confirmRecord).map(([confirmId, confirmNode]) => { + return
{confirmNode}
; + })} +
+ ); +}; + +export default ConfirmProvider; diff --git a/packages/app/src/providers/global-modal-provider.tsx b/packages/app/src/providers/global-modal-provider.tsx new file mode 100644 index 0000000000..76a0365dfc --- /dev/null +++ b/packages/app/src/providers/global-modal-provider.tsx @@ -0,0 +1,123 @@ +/* eslint-disable @typescript-eslint/no-empty-function */ +import { + createContext, + useCallback, + useContext, + useEffect, + useState, +} from 'react'; +import type { PropsWithChildren } from 'react'; +import ShortcutsModal from '@/components/shortcuts-modal'; +import ContactModal from '@/components/contact-modal'; +import QuickSearch from '@/components/quick-search'; +import { ImportModal } from '@/components/import'; +import { LoginModal } from '@/components/login-modal'; + +type ModalContextValue = { + triggerShortcutsModal: () => void; + triggerContactModal: () => void; + triggerQuickSearchModal: (visible?: boolean) => void; + triggerImportModal: () => void; + triggerLoginModal: () => void; +}; +type ModalContextProps = PropsWithChildren>; +type ModalMap = { + contact: boolean; + shortcuts: boolean; + quickSearch: boolean; + import: boolean; + login: boolean; +}; + +export const ModalContext = createContext({ + triggerShortcutsModal: () => {}, + triggerContactModal: () => {}, + triggerQuickSearchModal: () => {}, + triggerImportModal: () => {}, + triggerLoginModal: () => {}, +}); + +export const useModal = () => useContext(ModalContext); + +export const ModalProvider = ({ + children, +}: PropsWithChildren) => { + const [modalMap, setModalMap] = useState({ + contact: false, + shortcuts: false, + quickSearch: false, + import: false, + login: false, + }); + + const triggerHandler = useCallback( + (key: keyof ModalMap, visible?: boolean) => { + setModalMap({ + ...modalMap, + [key]: visible ?? !modalMap[key], + }); + }, + [modalMap] + ); + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + window.triggerHandler = () => triggerHandler('login'); + }, [triggerHandler]); + + return ( + { + triggerHandler('shortcuts'); + }, + triggerContactModal: () => { + triggerHandler('contact'); + }, + triggerQuickSearchModal: (visible?) => { + triggerHandler('quickSearch', visible); + }, + triggerImportModal: () => { + triggerHandler('import'); + }, + triggerLoginModal: () => { + triggerHandler('login'); + }, + }} + > + { + triggerHandler('contact', false); + }} + > + { + triggerHandler('shortcuts', false); + }} + > + { + triggerHandler('quickSearch', false); + }} + > + { + triggerHandler('import', false); + }} + > + { + triggerHandler('login', false); + }} + /> + {children} + + ); +}; + +export default ModalProvider; diff --git a/packages/app/src/styles/themeProvider.tsx b/packages/app/src/providers/themeProvider.tsx similarity index 63% rename from packages/app/src/styles/themeProvider.tsx rename to packages/app/src/providers/themeProvider.tsx index 806a52ffe8..07616f28e7 100644 --- a/packages/app/src/styles/themeProvider.tsx +++ b/packages/app/src/providers/themeProvider.tsx @@ -1,33 +1,45 @@ +import { createContext, useContext, useEffect, useState } from 'react'; import { ThemeProvider as EmotionThemeProvider, Global, css, } from '@emotion/react'; -import { createContext, useEffect, useState } from 'react'; +import { + ThemeProvider as MuiThemeProvider, + createTheme as MuiCreateTheme, +} from '@mui/material/styles'; import type { PropsWithChildren } from 'react'; import { Theme, ThemeMode, ThemeProviderProps, ThemeProviderValue, -} from './types'; -import { getLightTheme, getDarkTheme, globalThemeVariables } from './theme'; -import { SystemThemeHelper, localStorageThemeHelper } from './utils'; -import { useEditor } from '@/components/editor-provider'; +} from '@/styles/types'; +import { + getLightTheme, + getDarkTheme, + globalThemeVariables, +} from '@/styles/theme'; +import { SystemThemeHelper, localStorageThemeHelper } from '@/styles/utils'; +import useCurrentPageMeta from '@/hooks/use-current-page-meta'; export const ThemeContext = createContext({ mode: 'light', + // eslint-disable-next-line @typescript-eslint/no-empty-function changeMode: () => {}, theme: getLightTheme('page'), }); +export const useTheme = () => useContext(ThemeContext); +const muiTheme = MuiCreateTheme(); + export const ThemeProvider = ({ defaultTheme = 'light', children, }: PropsWithChildren) => { const [theme, setTheme] = useState(defaultTheme); const [mode, setMode] = useState('auto'); - const { mode: editorMode } = useEditor(); + const { mode: editorMode = 'page' } = useCurrentPageMeta() || {}; const themeStyle = theme === 'light' ? getLightTheme(editorMode) : getDarkTheme(editorMode); const changeMode = (themeMode: ThemeMode) => { @@ -81,16 +93,24 @@ export const ThemeProvider = ({ // }, [mode]); return ( - - - {children} - + // Use MuiThemeProvider is just because some Transitions in Mui components need it + + + + + {children} + + + ); }; diff --git a/packages/app/src/styles/helper.ts b/packages/app/src/styles/helper.ts index 50955a254b..826b222a5c 100644 --- a/packages/app/src/styles/helper.ts +++ b/packages/app/src/styles/helper.ts @@ -17,6 +17,23 @@ export const displayFlex = ( alignContent, }; }; +export const displayInlineFlex = ( + justifyContent: CSSProperties['justifyContent'] = 'unset', + alignItems: CSSProperties['alignContent'] = 'unset', + alignContent: CSSProperties['alignContent'] = 'unset' +): { + display: CSSProperties['display']; + justifyContent: CSSProperties['justifyContent']; + alignItems: CSSProperties['alignContent']; + alignContent: CSSProperties['alignContent']; +} => { + return { + display: 'inline-flex', + justifyContent, + alignItems, + alignContent, + }; +}; export const absoluteCenter = ({ horizontal = false, @@ -82,3 +99,21 @@ export const fixedCenter = ({ })`, }; }; + +export const textEllipsis = (lineNum = 1): CSSProperties => { + if (lineNum > 1) { + return { + display: '-webkit-box', + wordBreak: 'break-all', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: `${lineNum}`, //需要显示的行数 + overflow: 'hidden', + textOverflow: 'ellipsis', + }; + } + return { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + }; +}; diff --git a/packages/app/src/styles/hooks.ts b/packages/app/src/styles/hooks.ts deleted file mode 100644 index 94398c7f36..0000000000 --- a/packages/app/src/styles/hooks.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { useContext } from 'react'; -import { ThemeContext } from './themeProvider'; - -export const useTheme = () => useContext(ThemeContext); diff --git a/packages/app/src/styles/index.ts b/packages/app/src/styles/index.ts index d65281b881..18a47af965 100644 --- a/packages/app/src/styles/index.ts +++ b/packages/app/src/styles/index.ts @@ -1,7 +1,5 @@ export type { ThemeMode, ThemeProviderProps, AffineTheme } from './types'; export * from './styled'; -export { ThemeProvider } from './themeProvider'; export * from './theme'; -export { useTheme } from './hooks'; export * from './helper'; diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts index e54ba4a4fd..1412e00285 100644 --- a/packages/app/src/styles/theme.ts +++ b/packages/app/src/styles/theme.ts @@ -16,18 +16,21 @@ export const getLightTheme = ( pageBackground: '#fff', hoverBackground: '#F1F3FF', + innerHoverBackground: '#E0E6FF', popoverBackground: '#fff', tooltipBackground: '#6880FF', codeBackground: '#f2f5f9', warningBackground: '#FFF9C7', + errorBackground: '#FFDED8', textColor: '#3A4C5C', edgelessTextColor: '#3A4C5C', - iconColor: '#9096A5', + iconColor: '#888a9e', linkColor: '#6880FF', linkColor2: '#6880FF', linkVisitedColor: '#ABB8FE', popoverColor: '#4C6275', + inputColor: '#4C6275', tooltipColor: '#fff', codeColor: '#517ea6', quoteColor: '#4C6275', @@ -36,13 +39,21 @@ export const getLightTheme = ( borderColor: '#D0D7E3', disableColor: '#C0C0C0', warningColor: '#906616', + errorColor: '#EB4335', }, font: { xs: '12px', sm: '16px', base: '18px', + h1: '30px', + h2: '28px', + h3: '26px', + h4: '24px', + h5: '22px', + h6: '20px', + family: `Avenir Next, Poppins, ${basicFontFamily}`, - family2: `Space Mono, ${basicFontFamily}`, + mono: `Space Mono, ${basicFontFamily}`, lineHeightBase: '26px', }, zIndex: { @@ -78,6 +89,7 @@ export const getDarkTheme = ( pageBackground: '#2c2c2c', hoverBackground: '#3C3C42', + innerHoverBackground: '#E0E6FF', popoverBackground: '#1F2021', tooltipBackground: '#1F2021', codeBackground: @@ -85,14 +97,16 @@ export const getDarkTheme = ( ? lightTheme.colors.codeBackground : '#505662', warningBackground: '#FFF9C7', + errorBackground: '#FFDED8', textColor: '#fff', edgelessTextColor: '#3A4C5C', - iconColor: '#9096A5', + iconColor: '#888a9e', linkColor: '#7D91FF', linkColor2: '#6880FF', linkVisitedColor: '#505FAB', popoverColor: '#C6CBD9', + inputColor: '#C6CBD9', tooltipColor: '#fff', codeColor: editorMode === 'edgeless' ? lightTheme.colors.codeColor : '#BDDBFD', @@ -102,6 +116,7 @@ export const getDarkTheme = ( borderColor: '#4D4C53', disableColor: '#4b4b4b', warningColor: '#906616', + errorColor: '#EB4335', }, shadow: { popover: @@ -137,6 +152,7 @@ export const globalThemeVariables: ( '--affine-link-visited-color': theme.colors.linkVisitedColor, '--affine-icon-color': theme.colors.iconColor, '--affine-popover-color': theme.colors.popoverColor, + '--affine-input-color': theme.colors.inputColor, '--affine-code-color': theme.colors.codeColor, '--affine-quote-color': theme.colors.quoteColor, '--affine-selected-color': theme.colors.selectedColor, @@ -158,7 +174,7 @@ export const globalThemeVariables: ( '--affine-z-index-popover': theme.zIndex.popover, '--affine-font-family': theme.font.family, - '--affine-font-family2': theme.font.family2, + '--affine-font-mono': theme.font.mono, '--affine-paragraph-space': theme.space.paragraph, '--affine-popover-radius': theme.radius.popover, diff --git a/packages/app/src/styles/types.ts b/packages/app/src/styles/types.ts index 155842eebc..85b12df586 100644 --- a/packages/app/src/styles/types.ts +++ b/packages/app/src/styles/types.ts @@ -23,8 +23,10 @@ export interface AffineTheme { popoverBackground: string; tooltipBackground: string; hoverBackground: string; + innerHoverBackground: string; codeBackground: string; warningBackground: string; + errorBackground: string; // Use for the page`s text textColor: string; // Use for the editor`s text, because in edgeless mode text is different form other @@ -35,6 +37,7 @@ export interface AffineTheme { linkVisitedColor: string; iconColor: string; popoverColor: string; + inputColor: string; tooltipColor: string; codeColor: string; quoteColor: string; @@ -43,14 +46,21 @@ export interface AffineTheme { borderColor: string; disableColor: string; warningColor: string; + errorColor: string; }; font: { xs: string; // tiny sm: string; // small base: string; + h1: string; + h2: string; + h3: string; + h4: string; + h5: string; + h6: string; family: string; - family2: string; + mono: string; lineHeightBase: string | number; }; @@ -111,7 +121,7 @@ export interface AffineThemeCSSVariables { '--affine-z-index-popover': AffineTheme['zIndex']['popover']; '--affine-font-family': AffineTheme['font']['family']; - '--affine-font-family2': AffineTheme['font']['family2']; + '--affine-font-mono': AffineTheme['font']['mono']; '--affine-paragraph-space': AffineTheme['space']['paragraph']; @@ -119,5 +129,6 @@ export interface AffineThemeCSSVariables { } declare module '@emotion/react' { + // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface Theme extends AffineTheme {} } diff --git a/packages/app/src/templates/AFFiNE-Docs.md b/packages/app/src/templates/AFFiNE-Docs.md new file mode 100644 index 0000000000..be3f0bde97 --- /dev/null +++ b/packages/app/src/templates/AFFiNE-Docs.md @@ -0,0 +1,25 @@ +# AFFiNE Docs + +This repo is the source for AFFiNE documentation. + +The published documentation is at: + +- https://docs.affine.pro + +Please [file an issue](https://github.com/toeverything/AFFiNE-docs/issues) if you can't find what you are looking for. + +## Get Started + +This repo is split into folders - with each folder being a seperate category of documentation. + +You can browse the folders to view the pages within each category. + +Please note: Some pages may not display correctly on GitHub, be sure to check the corresponding page on GitBook. + +### Asset Naming + +All assets should be stored in `.gitbook/assets/`. + +The asset should be named according to where it will be used. + +If an image is needed for `/developer-docs/coding/setup.md` the expected filename would be `developer-docs_coding_setup` where `_` seperates each folder/file. And if multiple images are required a unique filename can be appeneded, such as `_01`. diff --git a/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md new file mode 100644 index 0000000000..96223e5f3f --- /dev/null +++ b/packages/app/src/templates/Welcome-to-AFFiNE-Alpha-v2.0.md @@ -0,0 +1,50 @@ +We've been working hard on improving the user experience and have made several new features live for you to use. + +If you are looking for **support**, would like to **suggest** your ideas, and keep on top of all the **latest updates**. We suggest you visit the [AFFiNE Community](https://community.affine.pro/home) site. + +Let us know what you think of this latest version. + +**AFFiNE Alpha brings:** + +1. A much smoother editing experience, with much greater stability; +2. More complete Markdown support and improved keyboard shortcuts; +3. New features such as dark mode; Switch between view styles using the ☀ and 🌙. +4. Clean and modern UI/UX design. + +**Looking for Markdown syntax or keyboard shortcuts?** + +- Find the (?) in the bottom right, then the ️⌨️, to view a full list of Keyboard Shortcuts + +### In this release, you can now: + +- Manage your pages from the collapsible **sidebar**, which allows you to add **favourites** and restore deleted files from the **trash** +- Search through all your content with the quick search - activate with `Ctrl/⌘ + ?` +- Quickly format text with the **pop-up toolbar** (highlight any text to give it a try) +- Copy and paste **images** into your pages, resize them and add captions +- Add horizontal line dividers to your text with `---` and `***` +- Changes are saved **locally**, but we still recommend you export your data to avoid data loss +- Insert code blocks with syntax highlighting support using ``` + +### Playground: + +[] Try a horiztaonl line: `---` + +[] What about a code block? ``` + +        console.log('Hello world'); + +**How about page management?** + +[] Create a new page + +[] Send a page to trash + +[] Favourite a page + +**Have an enjoyable editing experience !!!** 😃 + +Have some feedback or just want to get in touch? Use the (?), then 🎧 to get in touch and join our communities. + +**Interested in AFFiNE cloud?** + +Head over to [general](https://community.affine.pro/c/general-discussion/) in our community and post what interests you about AFFiNE! diff --git a/packages/app/src/templates/Welcome-to-the-AFFiNE-Alpha.md b/packages/app/src/templates/Welcome-to-the-AFFiNE-Alpha.md new file mode 100644 index 0000000000..a11f3031ef --- /dev/null +++ b/packages/app/src/templates/Welcome-to-the-AFFiNE-Alpha.md @@ -0,0 +1,59 @@ +The AFFiNE Alpha is here! You can also view our [Official Website](https://affine.pro/)! + +**What's different in AFFiNE Alpha?** + +1. A much ~smoother editing~ experience, with much ~greater stability~; +2. More complete ~Markdown~ support and improved ~keyboard shortcuts~; +3. New features such as ~dark mode~; + - **Switch between view styles using the** ☀ **and** 🌙. +4. ~Clean and modern UI/UX~ design. + +**Looking for Markdown syntax or keyboard shortcuts?** + +- Find the (?) in the bottom right, then the ️⌨️, to view a full list of `Keyboard Shortcuts` + +**Have an enjoyable editing experience !!!** 😃 + +Have some feedback or just want to get in touch? Use the (?), then 🎧 to get in touch and join our communities. + +**Still in development** + +There are also some things you should consider about this AFFiNE Alpha including some ~limitations~: + +- Single page editing - currently editing multiple docs/pages is not supported; +- Changes are not automatically stored, to save changes you should export your data. Options can be found by going to the top right and finding the `⋮` icon; +- Without an import/open feature you are still able to access your data by copying it back in. + +**Keyboard Shortcuts:** + +1. Undo: `⌘+Z` or `Ctrl+Z` +2. Redo: `⌘+⇧+Z` or `Ctrl+Shift+Z` +3. Bold: `⌘+B` or `Ctrl+B` +4. Italic: `⌘+I` or `Ctrl+I` +5. Underline: `⌘+U` or `Ctrl+U` +6. Strikethrough: `⌘+⇧+S` or `Ctrl+Shift+S` +7. Inline code: `⌘+E` or `Ctrl+E` +8. Link: `⌘+K` or `Ctrl+K` +9. Body text: `⌘+⌥+0` or `Ctrl+Shift+0` +10. Heading 1: `⌘+⌥+1` or `Ctrl+Shift+1` +11. Heading 2: `⌘+⌥+2` or `Ctrl+Shift+2` +12. Heading 3: `⌘+⌥+3` or `Ctrl+Shift+3` +13. Heading 4: `⌘+⌥+4` or `Ctrl+Shift+4` +14. Heading 5: `⌘+⌥+5` or `Ctrl+Shift+5` +15. Heading 6: `⌘+⌥+6` or `Ctrl+Shift+6` +16. Increase indent: `Tab` +17. Reduce indent: `⇧+Tab` or `Shift+Tab` + +**Markdown Syntax:** + +1. Bold: `**text**` +2. Italic: `*text*` +3. Underline: `~text~` +4. Strikethrough: `~~text~~` +5. Inline code: `` `text` `` +6. Heading 1: `# text` +7. Heading 2: `## text` +8. Heading 3: `### text` +9. Heading 4: `#### text` +10. Heading 5: `##### text` +11. Heading 6: `###### text` diff --git a/packages/app/src/ui/button/Button.tsx b/packages/app/src/ui/button/Button.tsx new file mode 100644 index 0000000000..2af171b1f8 --- /dev/null +++ b/packages/app/src/ui/button/Button.tsx @@ -0,0 +1,62 @@ +import { cloneElement, Children, forwardRef } from 'react'; +import { StyledButton } from './styles'; + +import { ButtonProps } from './interface'; +import { getSize } from './utils'; +import { Loading } from './Loading'; + +export const Button = forwardRef( + ( + { + size = 'default', + disabled = false, + hoverBackground, + hoverColor, + hoverStyle, + shape = 'default', + icon, + type = 'default', + children, + bold = false, + loading = false, + ...props + }, + ref + ) => { + const { iconSize } = getSize(size); + + return ( + + {loading ? ( + + ) : ( + <> + {icon && + cloneElement(Children.only(icon), { + width: iconSize, + height: iconSize, + className: `affine-button-icon ${icon.props.className ?? ''}`, + })} + {children && {children}} + + )} + + ); + } +); +Button.displayName = 'Button'; + +export default Button; diff --git a/packages/app/src/ui/button/IconButton.tsx b/packages/app/src/ui/button/IconButton.tsx new file mode 100644 index 0000000000..5166af1a3e --- /dev/null +++ b/packages/app/src/ui/button/IconButton.tsx @@ -0,0 +1,86 @@ +import { + HTMLAttributes, + cloneElement, + ReactElement, + Children, + CSSProperties, + forwardRef, +} from 'react'; +import { StyledIconButton } from './styles'; + +const SIZE_SMALL = 'small' as const; +const SIZE_MIDDLE = 'middle' as const; +const SIZE_NORMAL = 'normal' as const; +// TODO: IconButton should merge into Button, but it has not been designed yet +const SIZE_CONFIG = { + [SIZE_SMALL]: { + iconSize: 16, + areaSize: 24, + }, + [SIZE_MIDDLE]: { + iconSize: 20, + areaSize: 28, + }, + [SIZE_NORMAL]: { + iconSize: 24, + areaSize: 32, + }, +} as const; + +export type IconButtonProps = { + size?: + | typeof SIZE_SMALL + | typeof SIZE_MIDDLE + | typeof SIZE_NORMAL + | [number, number]; + iconSize?: + | typeof SIZE_SMALL + | typeof SIZE_MIDDLE + | typeof SIZE_NORMAL + | [number, number]; + disabled?: boolean; + hoverBackground?: CSSProperties['background']; + hoverColor?: string; + hoverStyle?: CSSProperties; + children: ReactElement, 'svg'>; + darker?: boolean; +} & HTMLAttributes; + +export const IconButton = forwardRef( + ( + { + size = 'normal', + iconSize = 'normal', + disabled = false, + children, + ...props + }, + ref + ) => { + const [width, height] = Array.isArray(size) + ? size + : [SIZE_CONFIG[size]['areaSize'], SIZE_CONFIG[size]['areaSize']]; + const [iconWidth, iconHeight] = Array.isArray(iconSize) + ? iconSize + : [SIZE_CONFIG[iconSize]['iconSize'], SIZE_CONFIG[iconSize]['iconSize']]; + + return ( + + {cloneElement(Children.only(children), { + width: iconWidth, + height: iconHeight, + })} + + ); + } +); +IconButton.displayName = 'IconButton'; + +export default IconButton; diff --git a/packages/app/src/ui/button/Loading.tsx b/packages/app/src/ui/button/Loading.tsx new file mode 100644 index 0000000000..8bada7628a --- /dev/null +++ b/packages/app/src/ui/button/Loading.tsx @@ -0,0 +1,60 @@ +import { styled } from '@/styles'; +import { ButtonProps } from './interface'; +import { getButtonColors } from './utils'; + +export const LoadingContainer = styled('div')>( + ({ theme, type = 'default' }) => { + const { color } = getButtonColors(theme, type); + return ` + margin: 0px auto; + width: 38px; + text-align: center; + .load { + width: 8px; + height: 8px; + background-color: ${color}; + + border-radius: 100%; + display: inline-block; + -webkit-animation: bouncedelay 1.4s infinite ease-in-out; + animation: bouncedelay 1.4s infinite ease-in-out; + /* Prevent first frame from flickering when animation starts */ + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + } + .load1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } + .load2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + } + + @-webkit-keyframes bouncedelay { + 0%, 80%, 100% { -webkit-transform: scale(0) } + 40% { -webkit-transform: scale(1.0) } + } + + @keyframes bouncedelay { + 0%, 80%, 100% { + transform: scale(0); + -webkit-transform: scale(0); + } 40% { + transform: scale(1.0); + -webkit-transform: scale(1.0); + } + } + `; + } +); + +export const Loading = ({ type }: Pick) => { + return ( + +
+
+
+
+ ); +}; diff --git a/packages/app/src/ui/button/TextButton.tsx b/packages/app/src/ui/button/TextButton.tsx new file mode 100644 index 0000000000..50068ec8be --- /dev/null +++ b/packages/app/src/ui/button/TextButton.tsx @@ -0,0 +1,54 @@ +import { cloneElement, Children, forwardRef } from 'react'; +import { StyledTextButton } from './styles'; + +import { ButtonProps } from './interface'; +import { getSize } from './utils'; + +export const TextButton = forwardRef( + ( + { + size = 'default', + disabled = false, + hoverBackground, + hoverColor, + hoverStyle, + shape = 'default', + icon, + type = 'default', + children, + bold = false, + ...props + }, + ref + ) => { + const { iconSize } = getSize(size); + + return ( + + {icon && + cloneElement(Children.only(icon), { + width: iconSize, + height: iconSize, + className: `affine-button-icon ${icon.props.className ?? ''}`, + })} + {children && {children}} + + ); + } +); +TextButton.displayName = 'TextButton'; + +export default TextButton; diff --git a/packages/app/src/ui/button/index.ts b/packages/app/src/ui/button/index.ts new file mode 100644 index 0000000000..cae8df7c0c --- /dev/null +++ b/packages/app/src/ui/button/index.ts @@ -0,0 +1,3 @@ +export * from './IconButton'; +export * from './Button'; +export * from './TextButton'; diff --git a/packages/app/src/ui/button/interface.ts b/packages/app/src/ui/button/interface.ts new file mode 100644 index 0000000000..6b5a55996b --- /dev/null +++ b/packages/app/src/ui/button/interface.ts @@ -0,0 +1,24 @@ +import { + CSSProperties, + HTMLAttributes, + PropsWithChildren, + ReactElement, +} from 'react'; + +export const SIZE_SMALL = 'small' as const; +export const SIZE_MIDDLE = 'middle' as const; +export const SIZE_DEFAULT = 'default' as const; + +export type ButtonProps = PropsWithChildren & + Omit, 'type'> & { + size?: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT; + disabled?: boolean; + hoverBackground?: CSSProperties['background']; + hoverColor?: CSSProperties['color']; + hoverStyle?: CSSProperties; + icon?: ReactElement; + shape?: 'default' | 'round' | 'circle'; + type?: 'primary' | 'warning' | 'danger' | 'default'; + bold?: boolean; + loading?: boolean; + }; diff --git a/packages/app/src/ui/button/styles.ts b/packages/app/src/ui/button/styles.ts new file mode 100644 index 0000000000..54cda81dcc --- /dev/null +++ b/packages/app/src/ui/button/styles.ts @@ -0,0 +1,226 @@ +import { absoluteCenter, displayInlineFlex, styled } from '@/styles'; +import { CSSProperties } from 'react'; +import { ButtonProps } from '@/ui/button/interface'; +import { getSize, getButtonColors } from './utils'; + +export const StyledIconButton = styled('button', { + shouldForwardProp: prop => { + return ![ + 'borderRadius', + 'top', + 'right', + 'width', + 'height', + 'hoverBackground', + 'hoverColor', + 'hoverStyle', + 'darker', + ].includes(prop); + }, +})<{ + width: number; + height: number; + borderRadius: number; + disabled?: boolean; + hoverBackground?: CSSProperties['background']; + hoverColor?: string; + hoverStyle?: CSSProperties; + // In some cases, button is in a normal hover status, it should be darkened + darker?: boolean; +}>( + ({ + theme, + width, + height, + disabled, + hoverBackground, + hoverColor, + hoverStyle, + darker = false, + }) => { + return { + width, + height, + color: theme.colors.iconColor, + ...displayInlineFlex('center', 'center'), + position: 'relative', + ...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}), + transition: 'background .15s', + + // TODO: we need to add @emotion/babel-plugin + '::after': { + content: '""', + width, + height, + borderRadius: width / 5, + transition: 'background .15s', + ...absoluteCenter({ horizontal: true, vertical: true }), + }, + + svg: { + position: 'relative', + zIndex: 1, + }, + + ':hover': { + color: hoverColor ?? theme.colors.primaryColor, + '::after': { + background: + hoverBackground ?? darker + ? theme.colors.innerHoverBackground + : theme.colors.hoverBackground, + }, + ...(hoverStyle ?? {}), + }, + }; + } +); + +export const StyledTextButton = styled('button', { + shouldForwardProp: prop => { + return ![ + 'borderRadius', + 'top', + 'right', + 'width', + 'height', + 'hoverBackground', + 'hoverColor', + 'hoverStyle', + 'bold', + ].includes(prop); + }, +})< + Pick< + ButtonProps, + | 'size' + | 'disabled' + | 'hoverBackground' + | 'hoverColor' + | 'hoverStyle' + | 'shape' + | 'type' + | 'bold' + > +>( + ({ + theme, + size = 'default', + disabled, + hoverBackground, + hoverColor, + hoverStyle, + bold = false, + shape = 'default', + // TODO: Implement type + // eslint-disable-next-line @typescript-eslint/no-unused-vars + type = 'default', + }) => { + const { fontSize, borderRadius, padding, height } = getSize(size); + console.log('size', size, height); + + return { + height, + paddingLeft: padding, + paddingRight: padding, + ...displayInlineFlex('flex-start', 'center'), + position: 'relative', + ...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}), + transition: 'background .15s', + // TODO: Implement circle shape + borderRadius: shape === 'default' ? borderRadius : height / 2, + fontSize, + fontWeight: bold ? '500' : '400', + + ':hover': { + color: hoverColor ?? theme.colors.primaryColor, + background: hoverBackground ?? theme.colors.hoverBackground, + ...(hoverStyle ?? {}), + }, + }; + } +); + +export const StyledButton = styled('button', { + shouldForwardProp: prop => { + return ![ + 'hoverBackground', + 'hoverColor', + 'hoverStyle', + 'type', + 'bold', + ].includes(prop); + }, +})< + Pick< + ButtonProps, + | 'size' + | 'disabled' + | 'hoverBackground' + | 'hoverColor' + | 'hoverStyle' + | 'shape' + | 'type' + | 'bold' + > +>( + ({ + theme, + size = 'default', + disabled, + hoverBackground, + hoverColor, + hoverStyle, + bold = false, + shape = 'default', + type = 'default', + }) => { + const { fontSize, borderRadius, padding, height } = getSize(size); + + return { + height, + paddingLeft: padding, + paddingRight: padding, + border: '1px solid', + ...displayInlineFlex('flex-start', 'center'), + position: 'relative', + // TODO: disabled color is not decided + ...(disabled + ? { + cursor: 'not-allowed', + pointerEvents: 'none', + color: theme.colors.borderColor, + } + : {}), + transition: 'background .15s', + // TODO: Implement circle shape + borderRadius: shape === 'default' ? borderRadius : height / 2, + fontSize, + fontWeight: bold ? '500' : '400', + '.affine-button-icon': { + color: theme.colors.iconColor, + }, + '>span': { + marginLeft: '5px', + }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + ...getButtonColors(theme, type, { + hoverBackground, + hoverColor, + hoverStyle, + }), + + // TODO: disabled hover should be implemented + // + // ':hover': { + // color: hoverColor ?? theme.colors.primaryColor, + // background: hoverBackground ?? theme.colors.hoverBackground, + // '.affine-button-icon':{ + // + // } + // ...(hoverStyle ?? {}), + // }, + }; + } +); diff --git a/packages/app/src/ui/button/utils.ts b/packages/app/src/ui/button/utils.ts new file mode 100644 index 0000000000..fddfa5df6e --- /dev/null +++ b/packages/app/src/ui/button/utils.ts @@ -0,0 +1,104 @@ +import { AffineTheme } from '@/styles'; +import { + SIZE_SMALL, + SIZE_MIDDLE, + SIZE_DEFAULT, + ButtonProps, +} from './interface'; + +// TODO: Designer is not sure about the size, Now, is just use default size +export const SIZE_CONFIG = { + [SIZE_SMALL]: { + iconSize: 16, + fontSize: 16, + borderRadius: 6, + height: 26, + padding: 24, + }, + [SIZE_MIDDLE]: { + iconSize: 20, + fontSize: 16, + borderRadius: 6, + height: 32, + padding: 24, + }, + [SIZE_DEFAULT]: { + iconSize: 24, + fontSize: 16, + height: 38, + padding: 24, + borderRadius: 6, + }, +} as const; + +export const getSize = ( + size: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT +) => { + return SIZE_CONFIG[size]; +}; + +export const getButtonColors = ( + theme: AffineTheme, + type: ButtonProps['type'], + extend?: { + hoverBackground: ButtonProps['hoverBackground']; + hoverColor: ButtonProps['hoverColor']; + hoverStyle: ButtonProps['hoverStyle']; + } +) => { + switch (type) { + case 'primary': + return { + background: theme.colors.primaryColor, + color: '#fff', + borderColor: theme.colors.primaryColor, + '.affine-button-icon': { + color: '#fff', + }, + }; + case 'warning': + return { + background: theme.colors.warningBackground, + color: theme.colors.warningColor, + borderColor: theme.colors.warningBackground, + '.affine-button-icon': { + color: theme.colors.warningColor, + }, + ':hover': { + borderColor: theme.colors.warningColor, + color: extend?.hoverColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }; + case 'danger': + return { + background: theme.colors.errorBackground, + color: theme.colors.errorColor, + borderColor: theme.colors.errorBackground, + '.affine-button-icon': { + color: theme.colors.errorColor, + }, + ':hover': { + borderColor: theme.colors.errorColor, + color: extend?.hoverColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }; + default: + return { + color: theme.colors.popoverColor, + borderColor: theme.colors.borderColor, + ':hover': { + borderColor: theme.colors.primaryColor, + color: extend?.hoverColor ?? theme.colors.primaryColor, + '.affine-button-icon': { + color: extend?.hoverColor ?? theme.colors.primaryColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }, + }; + } +}; diff --git a/packages/app/src/ui/confirm/Confirm.tsx b/packages/app/src/ui/confirm/Confirm.tsx new file mode 100644 index 0000000000..d3de35adb1 --- /dev/null +++ b/packages/app/src/ui/confirm/Confirm.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { Modal, ModalCloseButton, ModalProps } from '../modal'; +import { + StyledButtonWrapper, + StyledConfirmContent, + StyledConfirmTitle, + StyledModalWrapper, +} from '@/ui/confirm/styles'; +import { Button } from '@/ui/button'; +export type ConfirmProps = { + title?: string; + content?: string; + confirmText?: string; + // TODO: Confirm button's color should depend on confirm type + confirmType?: 'primary' | 'warning' | 'danger'; + onConfirm?: () => void; + onCancel?: () => void; +} & Omit; + +export const Confirm = ({ + title, + content, + confirmText, + confirmType, + onConfirm, + onCancel, +}: ConfirmProps) => { + const [open, setOpen] = useState(true); + return ( + + + { + setOpen(false); + onCancel?.(); + }} + /> + {title} + {content} + + + + + + + + ); +}; + +export default Confirm; diff --git a/packages/app/src/ui/confirm/index.ts b/packages/app/src/ui/confirm/index.ts new file mode 100644 index 0000000000..d8e1c97b8c --- /dev/null +++ b/packages/app/src/ui/confirm/index.ts @@ -0,0 +1 @@ +export * from './Confirm'; diff --git a/packages/app/src/ui/confirm/styles.ts b/packages/app/src/ui/confirm/styles.ts new file mode 100644 index 0000000000..ef2c86155d --- /dev/null +++ b/packages/app/src/ui/confirm/styles.ts @@ -0,0 +1,34 @@ +import { displayFlex, styled } from '@/styles'; +import { ModalWrapper } from '@/ui/modal'; + +export const StyledModalWrapper = styled(ModalWrapper)(() => { + return { + width: '460px', + padding: '46px 60px 32px', + }; +}); + +export const StyledConfirmTitle = styled.div(({ theme }) => { + return { + fontSize: theme.font.h6, + fontWeight: 600, + textAlign: 'center', + color: theme.colors.popoverColor, + }; +}); + +export const StyledConfirmContent = styled.div(({ theme }) => { + return { + fontSize: theme.font.base, + textAlign: 'center', + marginTop: '12px', + color: theme.colors.textColor, + }; +}); + +export const StyledButtonWrapper = styled.div(() => { + return { + ...displayFlex('center', 'center'), + marginTop: '32px', + }; +}); diff --git a/packages/app/src/ui/divider/index.ts b/packages/app/src/ui/divider/index.ts new file mode 100644 index 0000000000..9dd7505d63 --- /dev/null +++ b/packages/app/src/ui/divider/index.ts @@ -0,0 +1,8 @@ +import MuiDivider from '@mui/material/Divider'; +import { styled } from '@/styles'; + +export const Divider = styled(MuiDivider)(({ theme }) => { + return { + borderColor: theme.colors.borderColor, + }; +}); diff --git a/packages/app/src/ui/empty/Empty.tsx b/packages/app/src/ui/empty/Empty.tsx new file mode 100644 index 0000000000..9aac859ba0 --- /dev/null +++ b/packages/app/src/ui/empty/Empty.tsx @@ -0,0 +1,20 @@ +import { CSSProperties } from 'react'; +import { EmptySVG } from './EmptySVG'; +import { styled } from '@/styles'; + +export type ContentProps = { + width?: CSSProperties['width']; + height?: CSSProperties['height']; + fontSize?: CSSProperties['fontSize']; +}; +export const Empty = styled(EmptySVG)( + ({ fontSize, width, height }) => { + return { + width, + height, + fontSize, + }; + } +); + +export default Empty; diff --git a/packages/app/src/ui/empty/EmptySVG.tsx b/packages/app/src/ui/empty/EmptySVG.tsx new file mode 100644 index 0000000000..3db455a137 --- /dev/null +++ b/packages/app/src/ui/empty/EmptySVG.tsx @@ -0,0 +1,87 @@ +import SvgIcon, { SvgIconProps } from '@mui/material/SvgIcon'; + +export const EmptySVG = (props: SvgIconProps) => { + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/packages/app/src/ui/empty/emptyPage.svg b/packages/app/src/ui/empty/emptyPage.svg new file mode 100644 index 0000000000..e5a4f4b32f --- /dev/null +++ b/packages/app/src/ui/empty/emptyPage.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/app/src/ui/empty/index.ts b/packages/app/src/ui/empty/index.ts new file mode 100644 index 0000000000..47dea78504 --- /dev/null +++ b/packages/app/src/ui/empty/index.ts @@ -0,0 +1 @@ +export * from './Empty'; diff --git a/packages/app/src/ui/input/Input.tsx b/packages/app/src/ui/input/Input.tsx new file mode 100644 index 0000000000..b3a702e4f3 --- /dev/null +++ b/packages/app/src/ui/input/Input.tsx @@ -0,0 +1,55 @@ +import { InputHTMLAttributes, useEffect, useState } from 'react'; +import { StyledInput } from './style'; + +type inputProps = { + value?: string; + placeholder?: string; + disabled?: boolean; + width?: number; + maxLength?: number; + minLength?: number; + onChange?: (value: string) => void; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + onBlur?: (e: any) => void; +}; + +export const Input = (props: inputProps) => { + const { + disabled, + value: valueProp, + placeholder, + maxLength, + minLength, + width = 260, + onChange, + onBlur, + } = props; + const [value, setValue] = useState(valueProp || ''); + const handleChange: InputHTMLAttributes['onChange'] = e => { + if ( + (maxLength && e.target.value.length > maxLength) || + (minLength && e.target.value.length < minLength) + ) + return; + setValue(e.target.value); + onChange && onChange(e.target.value); + }; + const handleBlur: InputHTMLAttributes['onBlur'] = e => { + onBlur && onBlur(e); + }; + useEffect(() => { + setValue(valueProp || ''); + }, [valueProp]); + return ( + + ); +}; diff --git a/packages/app/src/ui/input/index.ts b/packages/app/src/ui/input/index.ts new file mode 100644 index 0000000000..4b9f57f786 --- /dev/null +++ b/packages/app/src/ui/input/index.ts @@ -0,0 +1,3 @@ +export * from './Input'; +import { Input } from './Input'; +export default Input; diff --git a/packages/app/src/ui/input/style.ts b/packages/app/src/ui/input/style.ts new file mode 100644 index 0000000000..fd0e4d21f1 --- /dev/null +++ b/packages/app/src/ui/input/style.ts @@ -0,0 +1,30 @@ +import { styled } from '@/styles'; + +export const StyledInput = styled('input')<{ + disabled?: boolean; + value?: string; + width: number; +}>(({ theme, width, disabled }) => { + const fontWeight = 400; + const fontSize = '16px'; + return { + width: `${width}px`, + lineHeight: '22px', + padding: '8px 12px', + fontWeight, + fontSize, + color: disabled ? theme.colors.disableColor : theme.colors.inputColor, + border: `1px solid`, + borderColor: theme.colors.borderColor, // TODO: check out disableColor, + backgroundColor: theme.colors.popoverBackground, + borderRadius: '10px', + '&::placeholder': { + fontWeight, + fontSize, + color: theme.colors.placeHolderColor, + }, + '&:focus': { + borderColor: theme.colors.primaryColor, + }, + }; +}); diff --git a/packages/app/src/ui/layout/Content.tsx b/packages/app/src/ui/layout/Content.tsx new file mode 100644 index 0000000000..510f3886a6 --- /dev/null +++ b/packages/app/src/ui/layout/Content.tsx @@ -0,0 +1,45 @@ +import { CSSProperties } from 'react'; +import { styled, textEllipsis } from '@/styles'; + +// This component should be just used to be contained the text content +export type ContentProps = { + width?: CSSProperties['width']; + maxWidth?: CSSProperties['maxWidth']; + align?: CSSProperties['textAlign']; + color?: CSSProperties['color']; + fontSize?: CSSProperties['fontSize']; + weight?: CSSProperties['fontWeight']; + lineHeight?: CSSProperties['lineHeight']; + ellipsis?: boolean; + lineNum?: number; + children: string; +}; + +export const Content = styled.div( + ({ + theme, + color, + fontSize, + weight, + lineHeight, + ellipsis, + lineNum, + width, + maxWidth, + align, + }) => { + return { + width, + maxWidth, + textAlign: align, + display: 'inline-block', + color: color ?? theme.colors.textColor, + fontSize: fontSize ?? theme.font.base, + fontWeight: weight ?? 400, + lineHeight: lineHeight ?? 1.5, + ...(ellipsis ? textEllipsis(lineNum) : {}), + }; + } +); + +export default Content; diff --git a/packages/app/src/ui/layout/Wrapper.tsx b/packages/app/src/ui/layout/Wrapper.tsx new file mode 100644 index 0000000000..00584b20f0 --- /dev/null +++ b/packages/app/src/ui/layout/Wrapper.tsx @@ -0,0 +1,49 @@ +import type { CSSProperties } from 'react'; +import { styled } from '@/styles'; + +export type WrapperProps = { + display?: CSSProperties['display']; + flexDirection?: CSSProperties['flexDirection']; + justifyContent?: CSSProperties['justifyContent']; + alignItems?: CSSProperties['alignItems']; + wrap?: boolean; + flexShrink?: CSSProperties['flexShrink']; + flexGrow?: CSSProperties['flexGrow']; +}; + +// Sometimes we just want to wrap a component with a div to set flex or other styles, but we don't want to create a new component for it. +export const Wrapper = styled('div', { + shouldForwardProp: prop => { + return ![ + 'display', + 'justifyContent', + 'alignItems', + 'wrap', + 'flexDirection', + 'flexShrink', + 'flexGrow', + ].includes(prop); + }, +})( + ({ + display = 'flex', + justifyContent = 'flex-start', + alignItems = 'center', + wrap = false, + flexDirection = 'row', + flexShrink = '0', + flexGrow = '0', + }) => { + return { + display, + justifyContent, + alignItems, + flexWrap: wrap ? 'wrap' : 'nowrap', + flexDirection, + flexShrink, + flexGrow, + }; + } +); + +export default Wrapper; diff --git a/packages/app/src/ui/layout/index.ts b/packages/app/src/ui/layout/index.ts new file mode 100644 index 0000000000..08a825ec01 --- /dev/null +++ b/packages/app/src/ui/layout/index.ts @@ -0,0 +1,2 @@ +export * from './Wrapper'; +export * from './Content'; diff --git a/packages/app/src/ui/menu/menu.tsx b/packages/app/src/ui/menu/Menu.tsx similarity index 100% rename from packages/app/src/ui/menu/menu.tsx rename to packages/app/src/ui/menu/Menu.tsx diff --git a/packages/app/src/ui/menu/MenuItem.tsx b/packages/app/src/ui/menu/MenuItem.tsx new file mode 100644 index 0000000000..45e40fb649 --- /dev/null +++ b/packages/app/src/ui/menu/MenuItem.tsx @@ -0,0 +1,35 @@ +import { + cloneElement, + forwardRef, + HTMLAttributes, + PropsWithChildren, + ReactElement, +} from 'react'; +import { StyledMenuItem, StyledArrow } from './styles'; + +export type IconMenuProps = PropsWithChildren<{ + isDir?: boolean; + icon?: ReactElement; +}> & + HTMLAttributes; + +export const MenuItem = forwardRef( + ({ isDir = false, icon, children, ...props }, ref) => { + return ( + + {icon && + cloneElement(icon, { + width: 16, + height: 16, + style: { + marginRight: 14, + }, + })} + {children} + {isDir ? : null} + + ); + } +); +MenuItem.displayName = 'MenuItem'; +export default MenuItem; diff --git a/packages/app/src/ui/menu/index.ts b/packages/app/src/ui/menu/index.ts index 8dde85f113..f6fcdd4d68 100644 --- a/packages/app/src/ui/menu/index.ts +++ b/packages/app/src/ui/menu/index.ts @@ -1,2 +1,3 @@ -export * from './menu'; -export { StyledMenuItem as MenuItem } from './styles'; +export * from './Menu'; +// export { StyledMenuItem as MenuItem } from './styles'; +export * from './MenuItem'; diff --git a/packages/app/src/ui/menu/styles.ts b/packages/app/src/ui/menu/styles.ts index 1b426217d6..8679b4a153 100644 --- a/packages/app/src/ui/menu/styles.ts +++ b/packages/app/src/ui/menu/styles.ts @@ -1,5 +1,6 @@ -import { styled } from '@/styles'; +import { displayFlex, styled } from '@/styles'; import StyledPopperContainer from '../shared/Container'; +import { ArrowRightIcon } from '@blocksuite/icons'; export const StyledMenuWrapper = styled(StyledPopperContainer)(({ theme }) => { return { @@ -12,23 +13,32 @@ export const StyledMenuWrapper = styled(StyledPopperContainer)(({ theme }) => { }; }); -export const StyledMenuItem = styled('div')<{ popperVisible?: boolean }>( - ({ theme, popperVisible }) => { - return { - borderRadius: '5px', - padding: '0 14px', +export const StyledArrow = styled(ArrowRightIcon)({ + position: 'absolute', + right: 0, + top: 0, + bottom: 0, + margin: 'auto', +}); - color: popperVisible - ? theme.colors.primaryColor - : theme.colors.popoverColor, - backgroundColor: popperVisible - ? theme.colors.hoverBackground - : 'transparent', +export const StyledMenuItem = styled.button<{ + isDir?: boolean; +}>(({ theme, isDir = false }) => { + return { + width: '100%', + borderRadius: '5px', + padding: '0 14px', + fontSize: '14px', + height: '32px', + ...displayFlex('flex-start', 'center'), + cursor: isDir ? 'pointer' : '', + position: 'relative', + color: theme.colors.popoverColor, + backgroundColor: 'transparent', - ':hover': { - color: theme.colors.primaryColor, - backgroundColor: theme.colors.hoverBackground, - }, - }; - } -); + ':hover': { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; +}); diff --git a/packages/app/src/ui/modal/Modal.tsx b/packages/app/src/ui/modal/Modal.tsx new file mode 100644 index 0000000000..705f56ab6a --- /dev/null +++ b/packages/app/src/ui/modal/Modal.tsx @@ -0,0 +1,52 @@ +import Fade from '@mui/material/Fade'; +import { StyledModal, StyledBackdrop } from './styles'; +import { ModalUnstyledOwnProps } from '@mui/base/ModalUnstyled'; + +const Backdrop = ({ + open, + ...other +}: { + open?: boolean; + className: string; +}) => { + return ( + + + + ); +}; + +export type ModalProps = { + wrapperPosition?: ['top' | 'bottom' | 'center', 'left' | 'right' | 'center']; +} & ModalUnstyledOwnProps; + +const transformConfig = { + top: 'flex-start', + bottom: 'flex-end', + center: 'center', + left: 'flex-start', + right: 'flex-end', +}; + +export const Modal = (props: ModalProps) => { + const { + wrapperPosition = ['center', 'center'], + open, + children, + ...otherProps + } = props; + const [vertical, horizontal] = wrapperPosition; + return ( + + {children} + + ); +}; + +export default Modal; diff --git a/packages/app/src/ui/modal/ModalCloseButton.tsx b/packages/app/src/ui/modal/ModalCloseButton.tsx new file mode 100644 index 0000000000..ee1dd2747b --- /dev/null +++ b/packages/app/src/ui/modal/ModalCloseButton.tsx @@ -0,0 +1,29 @@ +import { HTMLAttributes } from 'react'; +import { CloseIcon } from '@blocksuite/icons'; +import { IconButton, IconButtonProps } from '@/ui/button'; +import { styled } from '@/styles'; +export type ModalCloseButtonProps = { + top?: number; + right?: number; +} & Omit & + HTMLAttributes; + +const StyledIconButton = styled(IconButton)< + Pick +>(({ top, right }) => { + return { + position: 'absolute', + top: top ?? 6, + right: right ?? 6, + }; +}); + +export const ModalCloseButton = ({ ...props }: ModalCloseButtonProps) => { + return ( + + + + ); +}; + +export default ModalCloseButton; diff --git a/packages/app/src/ui/modal/ModalWrapper.tsx b/packages/app/src/ui/modal/ModalWrapper.tsx new file mode 100644 index 0000000000..f924b58966 --- /dev/null +++ b/packages/app/src/ui/modal/ModalWrapper.tsx @@ -0,0 +1,19 @@ +import { CSSProperties } from 'react'; +import { styled } from '@/styles'; + +export const ModalWrapper = styled.div<{ + width?: CSSProperties['width']; + height?: CSSProperties['height']; + minHeight?: CSSProperties['minHeight']; +}>(({ theme, width, height, minHeight }) => { + return { + width, + height, + minHeight, + backgroundColor: theme.colors.popoverBackground, + borderRadius: '12px', + position: 'relative', + }; +}); + +export default ModalWrapper; diff --git a/packages/app/src/ui/modal/index.tsx b/packages/app/src/ui/modal/index.tsx index 936be1b987..1d4c80d99d 100644 --- a/packages/app/src/ui/modal/index.tsx +++ b/packages/app/src/ui/modal/index.tsx @@ -1,4 +1,7 @@ -import Modal from './modal'; +import Modal from './Modal'; + +export * from './ModalCloseButton'; +export * from './ModalWrapper'; +export * from './Modal'; -export * from './modal'; export default Modal; diff --git a/packages/app/src/ui/modal/modal.tsx b/packages/app/src/ui/modal/modal.tsx deleted file mode 100644 index 42a9b373e1..0000000000 --- a/packages/app/src/ui/modal/modal.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import Fade from '@mui/material/Fade'; -import { StyledModal, StyledBackdrop } from './style'; -import { ModalUnstyledOwnProps } from '@mui/base/ModalUnstyled'; - -const Backdrop = ({ - open, - ...other -}: { - open?: boolean; - className: string; -}) => { - return ( - - - - ); -}; - -export type ModalProps = ModalUnstyledOwnProps; - -export const Modal = (props: ModalProps) => { - const { components, open, children, ...otherProps } = props; - return ( -
- - {children} - -
- ); -}; - -export default Modal; diff --git a/packages/app/src/ui/modal/style.ts b/packages/app/src/ui/modal/style.ts deleted file mode 100644 index 3b37e3c0f2..0000000000 --- a/packages/app/src/ui/modal/style.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { displayFlex, fixedCenter, styled } from '@/styles'; -import ModalUnstyled from '@mui/base/ModalUnstyled'; - -export const StyledBackdrop = styled.div<{ open?: boolean }>(({ open }) => { - return { - zIndex: '-1', - position: 'fixed', - right: '0', - bottom: '0', - top: '0', - left: '0', - backgroundColor: 'rgba(58, 76, 92, 0.2)', - }; -}); - -export const StyledModal = styled(ModalUnstyled)(({ theme }) => { - return { - width: '100vw', - height: '100vh', - position: 'fixed', - left: '0', - top: '0', - zIndex: theme.zIndex.modal, - ...displayFlex('center', 'center'), - '*': { - WebkitTapHighlightColor: 'transparent', - outline: 'none', - }, - }; -}); diff --git a/packages/app/src/ui/modal/styles.ts b/packages/app/src/ui/modal/styles.ts new file mode 100644 index 0000000000..4af9cb397c --- /dev/null +++ b/packages/app/src/ui/modal/styles.ts @@ -0,0 +1,47 @@ +import { styled } from '@/styles'; +import ModalUnstyled from '@mui/base/ModalUnstyled'; +import { Wrapper } from '@/ui/layout'; +import { CSSProperties } from 'react'; + +export const StyledBackdrop = styled.div({ + zIndex: '-1', + position: 'fixed', + right: '0', + bottom: '0', + top: '0', + left: '0', + backgroundColor: 'rgba(58, 76, 92, 0.2)', +}); + +export const StyledModal = styled(ModalUnstyled, { + shouldForwardProp: prop => { + return !['justifyContent', 'alignItems'].includes(prop); + }, +})<{ + alignItems: CSSProperties['alignItems']; + justifyContent: CSSProperties['justifyContent']; +}>(({ theme, alignItems, justifyContent }) => { + return { + width: '100vw', + height: '100vh', + display: 'flex', + alignItems, + justifyContent, + position: 'fixed', + left: '0', + top: '0', + zIndex: theme.zIndex.modal, + '*': { + WebkitTapHighlightColor: 'transparent', + outline: 'none', + }, + }; +}); + +export const StyledWrapper = styled(Wrapper)(() => { + return { + width: '100vw', + height: '100vh', + overflow: 'hidden', + }; +}); diff --git a/packages/app/src/ui/popper/Popper.tsx b/packages/app/src/ui/popper/Popper.tsx index 0e93a7f7cc..2ebf105881 100644 --- a/packages/app/src/ui/popper/Popper.tsx +++ b/packages/app/src/ui/popper/Popper.tsx @@ -108,6 +108,7 @@ export const Popper = ({ onClick: (e: MouseEvent) => { children.props.onClick?.(e); if (!hasClickTrigger || visibleControlledByParent) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore onClick?.(e); return; @@ -116,8 +117,9 @@ export const Popper = ({ }, onPointerEnter: onPointerEnterHandler, onPointerLeave: onPointerLeaveHandler, - className: anchorClassName, - popperVisible: visible, + className: `${anchorClassName ? anchorClassName + ' ' : ''}${ + children.props.className + }`, })} {content && ( {showArrow && ( + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore )} diff --git a/packages/app/src/ui/table/Table.tsx b/packages/app/src/ui/table/Table.tsx new file mode 100644 index 0000000000..fba647619d --- /dev/null +++ b/packages/app/src/ui/table/Table.tsx @@ -0,0 +1,30 @@ +import { PropsWithChildren, Children, ReactNode, HTMLAttributes } from 'react'; +import { StyledTable } from './styles'; + +const childrenHasEllipsis = (children: ReactNode | ReactNode[]): boolean => { + return Children.toArray(children).some(child => { + if (typeof child === 'object' && 'props' in child) { + if (!child.props.ellipsis && child.props.children) { + return childrenHasEllipsis(child.props.children); + } + return child.props.ellipsis ?? false; + } + + return false; + }); +}; + +export const Table = ({ + children, + ...props +}: PropsWithChildren>) => { + const tableLayout = childrenHasEllipsis(children) ? 'fixed' : 'auto'; + + return ( + + {children} + + ); +}; + +export default Table; diff --git a/packages/app/src/ui/table/TableBody.tsx b/packages/app/src/ui/table/TableBody.tsx new file mode 100644 index 0000000000..8780f48b2f --- /dev/null +++ b/packages/app/src/ui/table/TableBody.tsx @@ -0,0 +1,11 @@ +import { HTMLAttributes, PropsWithChildren } from 'react'; +import { StyledTableBody } from '@/ui/table/styles'; + +export const TableBody = ({ + children, + ...props +}: PropsWithChildren>) => { + return {children}; +}; + +export default TableBody; diff --git a/packages/app/src/ui/table/TableCell.tsx b/packages/app/src/ui/table/TableCell.tsx new file mode 100644 index 0000000000..c6aed80987 --- /dev/null +++ b/packages/app/src/ui/table/TableCell.tsx @@ -0,0 +1,14 @@ +import { HTMLAttributes, PropsWithChildren } from 'react'; +import { TableCellProps } from './interface'; +import { StyledTableCell } from './styles'; + +export const TableCell = ({ + children, + ...props +}: PropsWithChildren< + TableCellProps & HTMLAttributes +>) => { + return {children}; +}; + +export default TableCell; diff --git a/packages/app/src/ui/table/TableHead.tsx b/packages/app/src/ui/table/TableHead.tsx new file mode 100644 index 0000000000..d1b993af30 --- /dev/null +++ b/packages/app/src/ui/table/TableHead.tsx @@ -0,0 +1,11 @@ +import { HTMLAttributes, PropsWithChildren } from 'react'; +import { StyledTableHead } from './styles'; + +export const TableHead = ({ + children, + ...props +}: PropsWithChildren>) => { + return {children}; +}; + +export default TableHead; diff --git a/packages/app/src/ui/table/TableRow.tsx b/packages/app/src/ui/table/TableRow.tsx new file mode 100644 index 0000000000..b60ba1bf13 --- /dev/null +++ b/packages/app/src/ui/table/TableRow.tsx @@ -0,0 +1,11 @@ +import { HTMLAttributes, PropsWithChildren } from 'react'; +import { StyledTableRow } from './styles'; + +export const TableRow = ({ + children, + ...props +}: PropsWithChildren>) => { + return {children}; +}; + +export default TableRow; diff --git a/packages/app/src/ui/table/index.ts b/packages/app/src/ui/table/index.ts new file mode 100644 index 0000000000..62c1806aae --- /dev/null +++ b/packages/app/src/ui/table/index.ts @@ -0,0 +1,12 @@ +// import Table from '@mui/material/Table'; +// import TableBody from '@mui/material/TableBody'; +// import TableCell from '@mui/material/TableCell'; +// import TableHead from '@mui/material/TableHead'; +// import TableRow from '@mui/material/TableRow'; +// + +export * from './Table'; +export * from './TableRow'; +export * from './TableHead'; +export * from './TableCell'; +export * from './TableBody'; diff --git a/packages/app/src/ui/table/interface.ts b/packages/app/src/ui/table/interface.ts new file mode 100644 index 0000000000..30e8795e50 --- /dev/null +++ b/packages/app/src/ui/table/interface.ts @@ -0,0 +1,8 @@ +import { CSSProperties } from 'react'; + +export type TableCellProps = { + align?: 'left' | 'right' | 'center'; + ellipsis?: boolean; + proportion?: number; + style?: CSSProperties; +}; diff --git a/packages/app/src/ui/table/styles.ts b/packages/app/src/ui/table/styles.ts new file mode 100644 index 0000000000..009888e8b1 --- /dev/null +++ b/packages/app/src/ui/table/styles.ts @@ -0,0 +1,74 @@ +import { styled, textEllipsis } from '@/styles'; +import { TableCellProps } from './interface'; + +export const StyledTable = styled.table<{ tableLayout: 'auto' | 'fixed' }>( + ({ theme, tableLayout }) => { + return { + fontSize: theme.font.base, + color: theme.colors.textColor, + tableLayout, + width: '100%', + borderCollapse: 'separate', + borderSpacing: '0 25px', + }; + } +); + +export const StyledTableBody = styled.tbody(() => { + return { + fontWeight: 400, + }; +}); + +export const StyledTableCell = styled.td< + Pick +>(({ align = 'left', ellipsis = false, proportion }) => { + const width = proportion ? `${proportion * 100}%` : 'auto'; + return { + width, + height: '52px', + lineHeight: '52px', + padding: '0 30px', + boxSizing: 'border-box', + textAlign: align, + verticalAlign: 'middle', + ...(ellipsis ? textEllipsis(1) : {}), + overflowWrap: 'break-word', + }; +}); + +export const StyledTableHead = styled.thead(() => { + return { + fontWeight: 500, + tr: { + ':hover': { + td: { + background: 'unset', + }, + }, + }, + }; +}); + +export const StyledTableRow = styled.tr(({ theme }) => { + return { + marginBottom: '25px', + td: { + transition: 'background .15s', + }, + 'td:first-of-type': { + borderTopLeftRadius: '10px', + borderBottomLeftRadius: '10px', + }, + 'td:last-of-type': { + borderTopRightRadius: '10px', + borderBottomRightRadius: '10px', + }, + + ':hover': { + td: { + background: theme.colors.hoverBackground, + }, + }, + }; +}); diff --git a/packages/app/src/ui/toast/index.ts b/packages/app/src/ui/toast/index.ts new file mode 100644 index 0000000000..7ff6d415e7 --- /dev/null +++ b/packages/app/src/ui/toast/index.ts @@ -0,0 +1 @@ +export * from './toast'; diff --git a/packages/app/src/ui/toast/toast.ts b/packages/app/src/ui/toast/toast.ts new file mode 100644 index 0000000000..327dd9b000 --- /dev/null +++ b/packages/app/src/ui/toast/toast.ts @@ -0,0 +1,111 @@ +// Copyright: https://github.com/toeverything/blocksuite/commit/8032ef3ab97aefce01664b36502fc392c5db8b78#diff-bf5b41be21936f9165a8400c7f20e24d3dbc49644ba57b9258e0943f0dc1c464 +import { css, html, TemplateResult } from 'lit'; + +export const sleep = (ms = 0) => + new Promise(resolve => setTimeout(resolve, ms)); + +let ToastContainer: HTMLDivElement | null = null; + +/** + * DO NOT USE FOR USER INPUT + * See https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro/35385518#35385518 + */ +const htmlToElement = (html: string | TemplateResult) => { + const template = document.createElement('template'); + if (typeof html === 'string') { + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + } else { + const { strings, values } = html; + const v = [...values, '']; // + last emtpty part + template.innerHTML = strings.reduce((acc, cur, i) => acc + cur + v[i], ''); + } + return template.content.firstChild as T; +}; + +const createToastContainer = () => { + const styles = css` + position: fixed; + z-index: 9999; + top: 16px; + left: 16px; + right: 16px; + bottom: 78px; + pointer-events: none; + display: flex; + flex-direction: column-reverse; + align-items: center; + `; + const template = html`
`; + const element = htmlToElement(template); + document.body.appendChild(element); + return element; +}; + +export type ToastOptions = { + duration: number; +}; + +/** + * @example + * ```ts + * toast('Hello World'); + * ``` + */ +export const toast = ( + message: string, + { duration }: ToastOptions = { duration: 2500 } +) => { + if (!ToastContainer) { + ToastContainer = createToastContainer(); + } + + const styles = css` + max-width: 480px; + text-align: center; + font-family: var(--affine-font-family); + font-size: var(--affine-font-sm); + padding: 6px 12px; + margin: 10px 0 0 0; + color: var(--affine-tooltip-color); + background: var(--affine-tooltip-background); + box-shadow: var(--affine-tooltip-shadow); + border-radius: 10px; + transition: all 230ms cubic-bezier(0.21, 1.02, 0.73, 1); + opacity: 0; + `; + + const template = html`
`; + const element = htmlToElement(template); + // message is not trusted + element.textContent = message; + ToastContainer.appendChild(element); + + const fadeIn = [ + { + opacity: 0, + }, + { opacity: 1 }, + ]; + const options = { + duration: 230, + easing: 'cubic-bezier(0.21, 1.02, 0.73, 1)', + fill: 'forwards' as const, + }; // satisfies KeyframeAnimationOptions; + element.animate(fadeIn, options); + + setTimeout(async () => { + const fadeOut = fadeIn.reverse(); + const animation = element.animate(fadeOut, options); + await animation.finished; + element.style.maxHeight = '0'; + element.style.margin = '0'; + element.style.padding = '0'; + // wait for transition + await sleep(230); + element.remove(); + }, duration); + return element; +}; + +export default toast; diff --git a/packages/app/src/utils/env.ts b/packages/app/src/utils/env.ts new file mode 100644 index 0000000000..3d1c1e5946 --- /dev/null +++ b/packages/app/src/utils/env.ts @@ -0,0 +1 @@ +export const isDev = process.env['NODE_ENV'] === 'development'; diff --git a/packages/app/src/utils/get-is-mobile.ts b/packages/app/src/utils/get-is-mobile.ts index b3a66796fe..1755a40061 100644 --- a/packages/app/src/utils/get-is-mobile.ts +++ b/packages/app/src/utils/get-is-mobile.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-useless-escape */ // Inspire by https://stackoverflow.com/a/11381730/8415727 export function isMobile(userAgent: string) { return !!( @@ -12,6 +13,7 @@ export function isMobile(userAgent: string) { export const getIsMobile = function () { return isMobile( + // eslint-disable-next-line @typescript-eslint/no-explicit-any navigator?.userAgent || navigator?.vendor || (window as any)?.opera ); }; diff --git a/packages/app/src/utils/index.ts b/packages/app/src/utils/index.ts new file mode 100644 index 0000000000..aeeaed611c --- /dev/null +++ b/packages/app/src/utils/index.ts @@ -0,0 +1,5 @@ +export { isDev } from './env'; + +export * from './useragent'; + +export * from './tools'; diff --git a/packages/app/src/utils/print-build-info.ts b/packages/app/src/utils/print-build-info.ts index 5acb994c8b..61e85c1763 100644 --- a/packages/app/src/utils/print-build-info.ts +++ b/packages/app/src/utils/print-build-info.ts @@ -1,9 +1,10 @@ import getConfig from 'next/config'; - +// import { isDev } from './env'; type Config = { BUILD_DATE: string; NODE_ENV: string; PROJECT_NAME: string; + EDITOR_VERSION: string; VERSION: string; CI?: string; COMMIT_HASH: string; @@ -13,9 +14,9 @@ const nextConfig = getConfig(); const publicRuntimeConfig: Config = nextConfig.publicRuntimeConfig; const printBuildInfo = () => { - if (process.env.NODE_ENV === 'development') { - return; - } + // if (isDev) { + // return; + // } console.group('Build info'); console.log('Project:', publicRuntimeConfig.PROJECT_NAME); console.log( @@ -28,6 +29,7 @@ const printBuildInfo = () => { 'Environment:', `${publicRuntimeConfig.NODE_ENV}${publicRuntimeConfig.CI ? '(ci)' : ''}` ); + console.log('Editor Version:', publicRuntimeConfig.EDITOR_VERSION); console.log('Version:', publicRuntimeConfig.VERSION); console.log( 'AFFiNE is an open source project, you can view its source code on GitHub!' diff --git a/packages/app/src/utils/tools.ts b/packages/app/src/utils/tools.ts new file mode 100644 index 0000000000..eaa7bc6774 --- /dev/null +++ b/packages/app/src/utils/tools.ts @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +// eslint-disable-next-line @typescript-eslint/ban-types +export function debounce(fn: Function, timeout: number) { + let timeoutId: any; + + return function (this: any) { + clearTimeout(timeoutId); + // eslint-disable-next-line prefer-rest-params + timeoutId = setTimeout(() => fn.apply(this, arguments), timeout); + }; +} diff --git a/packages/app/src/utils/useragent.ts b/packages/app/src/utils/useragent.ts new file mode 100644 index 0000000000..20b8593ef1 --- /dev/null +++ b/packages/app/src/utils/useragent.ts @@ -0,0 +1,82 @@ +function getUaHelper() { + let uaHelper = null; + (function (navigator) { + const getUa = () => { + const ua = navigator.userAgent; + const uas = ua.toLowerCase(); + const mobile = + /mobile|tablet|ip(ad|hone|od)|android/i.test(ua) || + (uas.indexOf('safari') > -1 && + /Mac OS/i.test(ua) && + /Macintosh/i.test(ua)); + const android = + (mobile && + (uas.indexOf('android') > -1 || uas.indexOf('linux') > -1)) || + uas.indexOf('adr') > -1; + const ios = mobile && !android && /Mac OS/i.test(ua); + const iphone = ios && uas.indexOf('iphone') > -1; + const ipad = ios && !iphone; + const wx = /MicroMessenger/i.test(ua); + const chrome = /CriOS/i.test(ua) || /Chrome/i.test(ua); + const tiktok = mobile && /aweme/i.test(ua); + const weibo = mobile && /Weibo/i.test(ua); + const safari = + ios && + !chrome && + !wx && + !weibo && + !tiktok && + /Safari|Macintosh/i.test(ua); + const firefox = /Firefox/.test(ua); + const win = /windows|win32|win64|wow32|wow64/.test(uas); + const linux = /linux/.test(uas); + return { + ua, + mobile, + android, + ios, + wx, + chrome, + iphone, + ipad, + safari, + tiktok, + weibo, + win, + linux, + firefox, + }; + }; + + class UaHelper { + private uaMap; + public isLinux = false; + public isMacOs = false; + public isSafari = false; + public isWindows = false; + public isFireFox = false; + + constructor() { + this.uaMap = getUa(); + this.initUaFlags(); + } + + public checkUseragent(isUseragent: keyof ReturnType) { + return Boolean(this.uaMap[isUseragent]); + } + + private initUaFlags() { + this.isLinux = this.checkUseragent('linux'); + this.isMacOs = this.checkUseragent('ios'); + this.isSafari = this.checkUseragent('safari'); + this.isWindows = this.checkUseragent('win'); + this.isFireFox = this.checkUseragent('firefox'); + } + } + uaHelper = new UaHelper(); + })(navigator); + + return uaHelper; +} + +export { getUaHelper }; diff --git a/packages/data-services/.gitignore b/packages/data-services/.gitignore new file mode 100644 index 0000000000..75e854d8dc --- /dev/null +++ b/packages/data-services/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/test-results/ +/playwright-report/ +/playwright/.cache/ diff --git a/packages/data-services/package.json b/packages/data-services/package.json new file mode 100644 index 0000000000..65bdc17129 --- /dev/null +++ b/packages/data-services/package.json @@ -0,0 +1,34 @@ +{ + "name": "@affine/data-services", + "version": "0.3.0", + "description": "", + "type": "module", + "main": "dist/src/index.js", + "types": "dist/src/index.d.ts", + "exports": { + "./src/*": "./dist/src/*.js", + ".": "./dist/src/index.js" + }, + "scripts": { + "build": "tsc --project ./tsconfig.json", + "test": "playwright test" + }, + "keywords": [], + "author": "", + "repository": { + "type": "git", + "url": "git+https://github.com/toeverything/AFFiNE.git" + }, + "devDependencies": { + "@playwright/test": "^1.29.1", + "typescript": "^4.8.4" + }, + "dependencies": { + "encoding": "^0.1.13", + "firebase": "^9.13.0", + "ky": "^0.33.0", + "lib0": "^0.2.58", + "swr": "^2.0.0", + "y-protocols": "^1.0.5" + } +} diff --git a/packages/data-services/playwright.config.ts b/packages/data-services/playwright.config.ts new file mode 100644 index 0000000000..a8e718991a --- /dev/null +++ b/packages/data-services/playwright.config.ts @@ -0,0 +1,25 @@ +import type { PlaywrightTestConfig } from '@playwright/test'; + +const config: PlaywrightTestConfig = { + testDir: './tests', + timeout: 30 * 1000, + expect: { + /** + * Maximum time expect() should wait for the condition to be met. + * For example in `await expect(locator).toHaveText();` + */ + timeout: 5000, + }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + use: { + actionTimeout: 0, + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, +}; + +export default config; diff --git a/packages/data-services/src/auth.ts b/packages/data-services/src/auth.ts new file mode 100644 index 0000000000..8fdd10be18 --- /dev/null +++ b/packages/data-services/src/auth.ts @@ -0,0 +1,44 @@ +import { initializeApp } from 'firebase/app'; +import { + getAuth, + createUserWithEmailAndPassword, + signInWithEmailAndPassword, + GoogleAuthProvider, + signInWithPopup, +} from 'firebase/auth'; +import type { User } from 'firebase/auth'; +import { token } from './request'; + +/** + * firebaseConfig reference: https://firebase.google.com/docs/web/setup#add_firebase_to_your_app + */ +const app = initializeApp({ + apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, + authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, + projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, + storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, + messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, + appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, + measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, +}); + +export const firebaseAuth = getAuth(app); + +const signUp = (email: string, password: string) => { + return createUserWithEmailAndPassword(firebaseAuth, email, password); +}; + +const signIn = (email: string, password: string) => { + return signInWithEmailAndPassword(firebaseAuth, email, password); +}; + +const googleAuthProvider = new GoogleAuthProvider(); +export const signInWithGoogle = async () => { + const user = await signInWithPopup(firebaseAuth, googleAuthProvider); + const idToken = await user.user.getIdToken(); + await token.initToken(idToken); +}; + +export const onAuthStateChanged = (callback: (user: User | null) => void) => { + firebaseAuth.onAuthStateChanged(callback); +}; diff --git a/packages/data-services/src/data-center.ts b/packages/data-services/src/data-center.ts new file mode 100644 index 0000000000..4c4f1e9916 --- /dev/null +++ b/packages/data-services/src/data-center.ts @@ -0,0 +1,19 @@ +class DataCenter { + static async init() { + return new DataCenter(); + } + + private constructor() { + // TODO + } +} + +let _dataCenterInstance: Promise; + +export const getDataCenter = () => { + if (!_dataCenterInstance) { + _dataCenterInstance = DataCenter.init(); + } + + return _dataCenterInstance; +}; diff --git a/packages/data-services/src/index.ts b/packages/data-services/src/index.ts new file mode 100644 index 0000000000..89de33b6b8 --- /dev/null +++ b/packages/data-services/src/index.ts @@ -0,0 +1,6 @@ +export { signInWithGoogle, onAuthStateChanged } from './auth'; +export * from './request'; +export * from './sdks'; +export * from './websocket'; + +export { getDataCenter } from './data-center'; diff --git a/packages/data-services/src/request/events.ts b/packages/data-services/src/request/events.ts new file mode 100644 index 0000000000..ae90c6756d --- /dev/null +++ b/packages/data-services/src/request/events.ts @@ -0,0 +1,28 @@ +import { AccessTokenMessage } from './token'; + +export type Callback = (user: AccessTokenMessage | null) => void; + +export class AuthorizationEvent { + private callbacks: Callback[] = []; + private lastState: AccessTokenMessage | null = null; + + /** + * Callback will execute when call this function. + */ + onChange(callback: Callback) { + this.callbacks.push(callback); + callback(this.lastState); + } + + triggerChange(user: AccessTokenMessage | null) { + this.lastState = user; + this.callbacks.forEach(callback => callback(user)); + } + + removeCallback(callback: Callback) { + const index = this.callbacks.indexOf(callback); + if (index > -1) { + this.callbacks.splice(index, 1); + } + } +} diff --git a/packages/data-services/src/request/index.ts b/packages/data-services/src/request/index.ts new file mode 100644 index 0000000000..8d0da14311 --- /dev/null +++ b/packages/data-services/src/request/index.ts @@ -0,0 +1,45 @@ +import ky from 'ky'; +import { token } from './token'; + +export const bareClient = ky.extend({ + retry: 1, + hooks: { + // afterResponse: [ + // async (_request, _options, response) => { + // if (response.status === 200) { + // const data = await response.json(); + // if (data.error) { + // return new Response(data.error.message, { + // status: data.error.code, + // }); + // } + // } + // return response; + // }, + // ], + }, +}); +export const client = bareClient.extend({ + hooks: { + beforeRequest: [ + async request => { + if (token.isLogin) { + if (token.isExpired) await token.refreshToken(); + request.headers.set('Authorization', token.token); + } else { + return new Response('Unauthorized', { status: 401 }); + } + }, + ], + + beforeRetry: [ + async ({ request }) => { + await token.refreshToken(); + request.headers.set('Authorization', token.token); + }, + ], + }, +}); + +export type { AccessTokenMessage } from './token'; +export { token }; diff --git a/packages/data-services/src/request/token.ts b/packages/data-services/src/request/token.ts new file mode 100644 index 0000000000..37f35c66f5 --- /dev/null +++ b/packages/data-services/src/request/token.ts @@ -0,0 +1,138 @@ +import { bareClient } from '.'; +import { AuthorizationEvent, Callback } from './events'; + +export interface AccessTokenMessage { + create_at: number; + exp: number; + email: string; + id: string; + name: string; + avatar_url: string; +} + +const TOKEN_KEY = 'affine_token'; + +type LoginParams = { + type: 'Google' | 'Refresh'; + token: string; +}; + +type LoginResponse = { + // JWT, expires in a very short time + token: string; + // Refresh token + refresh: string; +}; + +const login = (params: LoginParams): Promise => + bareClient.post('/api/user/token', { json: params }).json(); + +function b64DecodeUnicode(str: string) { + // Going backwards: from byte stream, to percent-encoding, to original string. + return decodeURIComponent( + window + .atob(str) + .split('') + .map(function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); + }) + .join('') + ); +} + +function getRefreshToken() { + try { + return localStorage.getItem(TOKEN_KEY) || ''; + } catch (_) { + return ''; + } +} +function setRefreshToken(token: string) { + try { + localStorage.setItem(TOKEN_KEY, token); + } catch (_) {} +} + +class Token { + private readonly _event: AuthorizationEvent; + private _accessToken: string; + private _refreshToken: string; + + private _user: AccessTokenMessage | null; + private _padding?: Promise; + + constructor(refreshToken?: string) { + this._accessToken = ''; + this._refreshToken = refreshToken || getRefreshToken(); + this._event = new AuthorizationEvent(); + + this._user = Token.parse(this._accessToken); + this._event.triggerChange(this._user); + } + + private _setToken(login: LoginResponse) { + this._accessToken = login.token; + this._refreshToken = login.refresh; + this._user = Token.parse(login.token); + + this._event.triggerChange(this._user); + + setRefreshToken(login.refresh); + } + + async initToken(token: string) { + this._setToken(await login({ token, type: 'Google' })); + } + + async refreshToken() { + if (!this._refreshToken) { + throw new Error('No authorization token.'); + } + if (!this._padding) { + this._padding = login({ + type: 'Refresh', + token: this._refreshToken, + }); + } + this._setToken(await this._padding); + this._padding = undefined; + } + + get token() { + return this._accessToken; + } + + get refresh() { + return this._refreshToken; + } + + get isLogin() { + return !!this._refreshToken; + } + + get isExpired() { + if (!this._user) return true; + return Date.now() - this._user.create_at > this._user.exp; + } + + static parse(token: string): AccessTokenMessage | null { + try { + const message: AccessTokenMessage = JSON.parse( + b64DecodeUnicode(token.split('.')[1]) + ); + return message; + } catch (error) { + return null; + } + } + + onChange(callback: Callback) { + this._event.onChange(callback); + } + + offChange(callback: Callback) { + this._event.removeCallback(callback); + } +} + +export const token = new Token(); diff --git a/packages/data-services/src/sdks/index.ts b/packages/data-services/src/sdks/index.ts new file mode 100644 index 0000000000..b55b3d62ac --- /dev/null +++ b/packages/data-services/src/sdks/index.ts @@ -0,0 +1,4 @@ +export * from './workspace'; +export * from './workspace.hook'; +export * from './user'; +export * from './user.hook'; diff --git a/packages/data-services/src/sdks/types/common.ts b/packages/data-services/src/sdks/types/common.ts new file mode 100644 index 0000000000..348bd13c91 --- /dev/null +++ b/packages/data-services/src/sdks/types/common.ts @@ -0,0 +1,2 @@ +export type CommonError = { error: { code: string; message: string } }; +export type MayError = Partial; diff --git a/packages/data-services/src/sdks/types/index.ts b/packages/data-services/src/sdks/types/index.ts new file mode 100644 index 0000000000..d0b9323665 --- /dev/null +++ b/packages/data-services/src/sdks/types/index.ts @@ -0,0 +1 @@ +export * from './common'; diff --git a/packages/data-services/src/sdks/user.hook.ts b/packages/data-services/src/sdks/user.hook.ts new file mode 100644 index 0000000000..86792a4f79 --- /dev/null +++ b/packages/data-services/src/sdks/user.hook.ts @@ -0,0 +1,23 @@ +import useSWR from 'swr'; +import type { SWRConfiguration } from 'swr'; +import { getUserByEmail } from './user'; +import type { GetUserByEmailParams, User } from './user'; + +export const GET_USER_BY_EMAIL_SWR_TOKEN = 'user.getUserByEmail'; +export function useGetUserByEmail( + params: GetUserByEmailParams, + config?: SWRConfiguration +) { + const { data, error, isLoading, mutate } = useSWR( + [GET_USER_BY_EMAIL_SWR_TOKEN, params], + ([_, params]) => getUserByEmail(params), + config + ); + + return { + loading: isLoading, + data, + error, + mutate, + }; +} diff --git a/packages/data-services/src/sdks/user.ts b/packages/data-services/src/sdks/user.ts new file mode 100644 index 0000000000..46ba1bc8a1 --- /dev/null +++ b/packages/data-services/src/sdks/user.ts @@ -0,0 +1,21 @@ +import { client } from '../request'; + +export interface GetUserByEmailParams { + email: string; + workspace_id: string; +} + +export interface User { + id: string; + name: string; + email: string; + avatar_url: string; + create_at: string; +} + +export async function getUserByEmail( + params: GetUserByEmailParams +): Promise { + const searchParams = new URLSearchParams({ ...params }); + return client.get('/api/user', { searchParams }).json(); +} diff --git a/packages/data-services/src/sdks/workspace.hook.ts b/packages/data-services/src/sdks/workspace.hook.ts new file mode 100644 index 0000000000..91fa3b9f3e --- /dev/null +++ b/packages/data-services/src/sdks/workspace.hook.ts @@ -0,0 +1,72 @@ +import useSWR from 'swr'; +import type { SWRConfiguration } from 'swr'; +import { + getWorkspaceDetail, + updateWorkspace, + deleteWorkspace, + inviteMember, + Workspace, +} from './workspace'; +import { + GetWorkspaceDetailParams, + WorkspaceDetail, + UpdateWorkspaceParams, + DeleteWorkspaceParams, + InviteMemberParams, + getWorkspaces, +} from './workspace'; + +export const GET_WORKSPACE_DETAIL_SWR_TOKEN = 'workspace.getWorkspaceDetail'; +export function useGetWorkspaceDetail( + params: GetWorkspaceDetailParams, + config?: SWRConfiguration +) { + const { data, error, isLoading, mutate } = useSWR( + [GET_WORKSPACE_DETAIL_SWR_TOKEN, params], + ([_, params]) => getWorkspaceDetail(params), + config + ); + + return { + data, + error, + loading: isLoading, + mutate, + }; +} + +export const GET_WORKSPACES_SWR_TOKEN = 'workspace.getWorkspaces'; +export function useGetWorkspaces(config?: SWRConfiguration) { + const { data, error, isLoading } = useSWR( + [GET_WORKSPACES_SWR_TOKEN], + () => getWorkspaces(), + config + ); + + return { + data, + error, + loading: isLoading, + }; +} + +export const UPDATE_WORKSPACE_SWR_TOKEN = 'workspace.updateWorkspace'; +/** + * I don't think a hook needed for update workspace. + * If you figure out the scene, please implement this function. + */ +export function useUpdateWorkspace() {} + +export const DELETE_WORKSPACE_SWR_TOKEN = 'workspace.deleteWorkspace'; +/** + * I don't think a hook needed for delete workspace. + * If you figure out the scene, please implement this function. + */ +export function useDeleteWorkspace() {} + +export const INVITE_MEMBER_SWR_TOKEN = 'workspace.inviteMember'; +/** + * I don't think a hook needed for invite member. + * If you figure out the scene, please implement this function. + */ +export function useInviteMember() {} diff --git a/packages/data-services/src/sdks/workspace.ts b/packages/data-services/src/sdks/workspace.ts new file mode 100644 index 0000000000..722e72410c --- /dev/null +++ b/packages/data-services/src/sdks/workspace.ts @@ -0,0 +1,178 @@ +import { client, bareClient } from '../request'; +import { User } from './user'; + +export interface GetWorkspaceDetailParams { + id: string; +} + +export enum WorkspaceType { + Private = 0, + Normal = 1, +} + +export enum PermissionType { + Read = 0, + Write = 1, + Admin = 2, + Owner = 99, +} + +export interface Workspace { + id: string; + type: WorkspaceType; + public: boolean; + permission_type: PermissionType; + create_at: number; +} + +export async function getWorkspaces(): Promise { + return client + .get('/api/workspace', { + headers: { + 'Cache-Control': 'no-cache', + }, + }) + .json(); +} + +export interface WorkspaceDetail extends Workspace { + owner: User; + member_count: number; +} + +export async function getWorkspaceDetail( + params: GetWorkspaceDetailParams +): Promise { + return client.get(`/api/workspace/${params.id}`).json(); +} + +export interface Permission { + id: number; + type: PermissionType; + workspace_id: string; + accepted: boolean; + create_at: number; +} + +export interface RegisteredUser extends User { + type: 'Registered'; +} + +export interface UnregisteredUser { + type: 'Unregistered'; + email: string; +} + +export interface Member extends Permission { + user: RegisteredUser | UnregisteredUser; +} + +export interface GetWorkspaceMembersParams { + id: string; +} + +export async function getWorkspaceMembers( + params: GetWorkspaceDetailParams +): Promise { + return client.get(`/api/workspace/${params.id}/permission`).json(); +} + +export interface CreateWorkspaceParams { + name: string; + avatar: string; +} + +export async function createWorkspace( + params: CreateWorkspaceParams +): Promise { + return client.post('/api/workspace', { json: params }).json(); +} + +export interface UpdateWorkspaceParams { + id: string; + public: boolean; +} + +export async function updateWorkspace( + params: UpdateWorkspaceParams +): Promise<{ public: boolean | null }> { + return client + .post(`/api/workspace/${params.id}`, { + json: { + public: params.public, + }, + }) + .json(); +} + +export interface DeleteWorkspaceParams { + id: string; +} + +export async function deleteWorkspace( + params: DeleteWorkspaceParams +): Promise { + await client.delete(`/api/workspace/${params.id}`); +} + +export interface InviteMemberParams { + id: string; + email: string; +} + +/** + * Notice: Only support normal(contrast to private) workspace. + */ +export async function inviteMember(params: InviteMemberParams): Promise { + return client + .post(`/api/workspace/${params.id}/permission`, { + json: { + email: params.email, + }, + }) + .json(); +} + +export interface RemoveMemberParams { + permissionId: number; +} + +export async function removeMember(params: RemoveMemberParams): Promise { + await client.delete(`/api/permission/${params.permissionId}`); +} + +export interface AcceptInvitingParams { + invitingCode: string; +} + +export async function acceptInviting( + params: AcceptInvitingParams +): Promise { + await bareClient.post(`/api/invitation/${params.invitingCode}`); +} + +export interface DownloadWOrkspaceParams { + workspaceId: string; +} +export async function downloadWorkspace( + params: DownloadWOrkspaceParams +): Promise { + return client.get(`/api/workspace/${params.workspaceId}/doc`).arrayBuffer(); +} + +export async function uploadBlob(params: { blob: Blob }): Promise { + return client.put('/api/blob', { body: params.blob }).text(); +} + +export async function getBlob(params: { + blobId: string; +}): Promise { + return client.get(`/api/blob/${params.blobId}`).arrayBuffer(); +} + +export interface LeaveWorkspaceParams { + id: number | string; +} +export async function leaveWorkspace({ id }: LeaveWorkspaceParams) { + await client.delete(`/api/workspace/${id}/permission`).json(); +} diff --git a/packages/data-services/src/websocket/index.ts b/packages/data-services/src/websocket/index.ts new file mode 100644 index 0000000000..d6651c22a2 --- /dev/null +++ b/packages/data-services/src/websocket/index.ts @@ -0,0 +1 @@ +export { WebsocketProvider } from './y-websocket'; diff --git a/packages/data-services/src/websocket/y-websocket.js b/packages/data-services/src/websocket/y-websocket.js new file mode 100644 index 0000000000..09004a51d1 --- /dev/null +++ b/packages/data-services/src/websocket/y-websocket.js @@ -0,0 +1,508 @@ +/* eslint-disable no-undef */ +/** + * @module provider/websocket + */ + +/* eslint-env browser */ + +// import * as Y from 'yjs'; // eslint-disable-line +import * as bc from 'lib0/broadcastchannel'; +import * as time from 'lib0/time'; +import * as encoding from 'lib0/encoding'; +import * as decoding from 'lib0/decoding'; +import * as syncProtocol from 'y-protocols/sync'; +import * as authProtocol from 'y-protocols/auth'; +import * as awarenessProtocol from 'y-protocols/awareness'; +import { Observable } from 'lib0/observable'; +import * as math from 'lib0/math'; +import * as url from 'lib0/url'; + +export const messageSync = 0; +export const messageQueryAwareness = 3; +export const messageAwareness = 1; +export const messageAuth = 2; + +/** + * encoder, decoder, provider, emitSynced, messageType + * @type {Array} + */ +const messageHandlers = []; + +messageHandlers[messageSync] = ( + encoder, + decoder, + provider, + emitSynced, + _messageType +) => { + encoding.writeVarUint(encoder, messageSync); + const syncMessageType = syncProtocol.readSyncMessage( + decoder, + encoder, + provider.doc, + provider + ); + if ( + emitSynced && + syncMessageType === syncProtocol.messageYjsSyncStep2 && + !provider.synced + ) { + provider.synced = true; + } +}; + +messageHandlers[messageQueryAwareness] = ( + encoder, + _decoder, + provider, + _emitSynced, + _messageType +) => { + encoding.writeVarUint(encoder, messageAwareness); + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate( + provider.awareness, + Array.from(provider.awareness.getStates().keys()) + ) + ); +}; + +messageHandlers[messageAwareness] = ( + _encoder, + decoder, + provider, + _emitSynced, + _messageType +) => { + awarenessProtocol.applyAwarenessUpdate( + provider.awareness, + decoding.readVarUint8Array(decoder), + provider + ); +}; + +messageHandlers[messageAuth] = ( + _encoder, + decoder, + provider, + _emitSynced, + _messageType +) => { + authProtocol.readAuthMessage(decoder, provider.doc, (_ydoc, reason) => + permissionDeniedHandler(provider, reason) + ); +}; + +// @todo - this should depend on awareness.outdatedTime +const messageReconnectTimeout = 30000; + +/** + * @param {WebsocketProvider} provider + * @param {string} reason + */ +const permissionDeniedHandler = (provider, reason) => + console.warn(`Permission denied to access ${provider.url}.\n${reason}`); + +/** + * @param {WebsocketProvider} provider + * @param {Uint8Array} buf + * @param {boolean} emitSynced + * @return {encoding.Encoder} + */ +const readMessage = (provider, buf, emitSynced) => { + const decoder = decoding.createDecoder(buf); + const encoder = encoding.createEncoder(); + const messageType = decoding.readVarUint(decoder); + const messageHandler = provider.messageHandlers[messageType]; + if (/** @type {any} */ (messageHandler)) { + messageHandler(encoder, decoder, provider, emitSynced, messageType); + } else { + console.error('Unable to compute message'); + } + return encoder; +}; + +/** + * @param {WebsocketProvider} provider + */ +const setupWS = provider => { + if (provider.shouldConnect && provider.ws === null) { + const websocket = new provider._WS(provider.url); + websocket.binaryType = 'arraybuffer'; + provider.ws = websocket; + provider.wsconnecting = true; + provider.wsconnected = false; + provider.synced = false; + + websocket.onmessage = event => { + provider.wsLastMessageReceived = time.getUnixTime(); + const encoder = readMessage(provider, new Uint8Array(event.data), true); + if (encoding.length(encoder) > 1) { + websocket.send(encoding.toUint8Array(encoder)); + } + }; + websocket.onerror = event => { + provider.emit('connection-error', [event, provider]); + }; + websocket.onclose = event => { + provider.emit('connection-close', [event, provider]); + provider.ws = null; + provider.wsconnecting = false; + if (provider.wsconnected) { + provider.wsconnected = false; + provider.synced = false; + // update awareness (all users except local left) + awarenessProtocol.removeAwarenessStates( + provider.awareness, + Array.from(provider.awareness.getStates().keys()).filter( + client => client !== provider.doc.clientID + ), + provider + ); + provider.emit('status', [ + { + status: 'disconnected', + }, + ]); + } else { + provider.wsUnsuccessfulReconnects++; + } + // Start with no reconnect timeout and increase timeout by + // using exponential backoff starting with 100ms + setTimeout( + setupWS, + math.min( + math.pow(2, provider.wsUnsuccessfulReconnects) * 100, + provider.maxBackoffTime + ), + provider + ); + }; + websocket.onopen = () => { + provider.wsLastMessageReceived = time.getUnixTime(); + provider.wsconnecting = false; + provider.wsconnected = true; + provider.wsUnsuccessfulReconnects = 0; + provider.emit('status', [ + { + status: 'connected', + }, + ]); + // always send sync step 1 when connected + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageSync); + syncProtocol.writeSyncStep1(encoder, provider.doc); + websocket.send(encoding.toUint8Array(encoder)); + // broadcast local awareness state + if (provider.awareness.getLocalState() !== null) { + const encoderAwarenessState = encoding.createEncoder(); + encoding.writeVarUint(encoderAwarenessState, messageAwareness); + encoding.writeVarUint8Array( + encoderAwarenessState, + awarenessProtocol.encodeAwarenessUpdate(provider.awareness, [ + provider.doc.clientID, + ]) + ); + websocket.send(encoding.toUint8Array(encoderAwarenessState)); + } + }; + + provider.emit('status', [ + { + status: 'connecting', + }, + ]); + } +}; + +/** + * @param {WebsocketProvider} provider + * @param {ArrayBuffer} buf + */ +const broadcastMessage = (provider, buf) => { + if (provider.wsconnected) { + /** @type {WebSocket} */ (provider.ws).send(buf); + } + if (provider.bcconnected) { + bc.publish(provider.bcChannel, buf, provider); + } +}; + +/** + * Websocket Provider for Yjs. Creates a websocket connection to sync the shared document. + * The document name is attached to the provided url. I.e. the following example + * creates a websocket connection to http://localhost:1234/my-document-name + * + * @example + * import * as Y from 'yjs' + * import { WebsocketProvider } from 'y-websocket' + * const doc = new Y.Doc() + * const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc) + * + * @extends {Observable} + */ +export class WebsocketProvider extends Observable { + /** + * @param {string} serverUrl + * @param {string} roomname + * @param {Y.Doc} doc + * @param {object} [opts] + * @param {boolean} [opts.connect] + * @param {awarenessProtocol.Awareness} [opts.awareness] + * @param {Object} [opts.params] + * @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill + * @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds + * @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff) + * @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication + */ + constructor( + serverUrl, + roomname, + doc, + { + connect = true, + awareness = new awarenessProtocol.Awareness(doc), + params = {}, + WebSocketPolyfill = WebSocket, + resyncInterval = -1, + maxBackoffTime = 2500, + disableBc = false, + } = {} + ) { + super(); + // ensure that url is always ends with / + while (serverUrl[serverUrl.length - 1] === '/') { + serverUrl = serverUrl.slice(0, serverUrl.length - 1); + } + const encodedParams = url.encodeQueryParams(params); + this.maxBackoffTime = maxBackoffTime; + this.bcChannel = serverUrl + '/' + roomname; + this.url = + serverUrl + + '/' + + roomname + + (encodedParams.length === 0 ? '' : '?' + encodedParams); + this.roomname = roomname; + this.doc = doc; + this._WS = WebSocketPolyfill; + this.awareness = awareness; + this.wsconnected = false; + this.wsconnecting = false; + this.bcconnected = false; + this.disableBc = disableBc; + this.wsUnsuccessfulReconnects = 0; + this.messageHandlers = messageHandlers.slice(); + /** + * @type {boolean} + */ + this._synced = false; + /** + * @type {WebSocket?} + */ + this.ws = null; + this.wsLastMessageReceived = 0; + /** + * Whether to connect to other peers or not + * @type {boolean} + */ + this.shouldConnect = connect; + + /** + * @type {number} + */ + this._resyncInterval = 0; + if (resyncInterval > 0) { + this._resyncInterval = /** @type {any} */ ( + setInterval(() => { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + // resend sync step 1 + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageSync); + syncProtocol.writeSyncStep1(encoder, doc); + this.ws.send(encoding.toUint8Array(encoder)); + } + }, resyncInterval) + ); + } + + /** + * @param {ArrayBuffer} data + * @param {any} origin + */ + this._bcSubscriber = (data, origin) => { + if (origin !== this) { + const encoder = readMessage(this, new Uint8Array(data), false); + if (encoding.length(encoder) > 1) { + bc.publish(this.bcChannel, encoding.toUint8Array(encoder), this); + } + } + }; + /** + * Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel) + * @param {Uint8Array} update + * @param {any} origin + */ + this._updateHandler = (update, origin) => { + if (origin !== this) { + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageSync); + syncProtocol.writeUpdate(encoder, update); + broadcastMessage(this, encoding.toUint8Array(encoder)); + } + }; + this.doc.on('update', this._updateHandler); + /** + * @param {any} changed + * @param {any} _origin + */ + this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => { + const changedClients = added.concat(updated).concat(removed); + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageAwareness); + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate(awareness, changedClients) + ); + broadcastMessage(this, encoding.toUint8Array(encoder)); + }; + this._unloadHandler = () => { + awarenessProtocol.removeAwarenessStates( + this.awareness, + [doc.clientID], + 'window unload' + ); + }; + if (typeof window !== 'undefined') { + window.addEventListener('unload', this._unloadHandler); + } else if (typeof process !== 'undefined') { + process.on('exit', this._unloadHandler); + } + awareness.on('update', this._awarenessUpdateHandler); + this._checkInterval = /** @type {any} */ ( + setInterval(() => { + if ( + this.wsconnected && + messageReconnectTimeout < + time.getUnixTime() - this.wsLastMessageReceived + ) { + // no message received in a long time - not even your own awareness + // updates (which are updated every 15 seconds) + /** @type {WebSocket} */ (this.ws).close(); + } + }, messageReconnectTimeout / 10) + ); + if (connect) { + this.connect(); + } + } + + /** + * @type {boolean} + */ + get synced() { + return this._synced; + } + + set synced(state) { + if (this._synced !== state) { + this._synced = state; + this.emit('synced', [state]); + this.emit('sync', [state]); + } + } + + destroy() { + if (this._resyncInterval !== 0) { + clearInterval(this._resyncInterval); + } + clearInterval(this._checkInterval); + this.disconnect(); + if (typeof window !== 'undefined') { + window.removeEventListener('unload', this._unloadHandler); + } else if (typeof process !== 'undefined') { + process.off('exit', this._unloadHandler); + } + this.awareness.off('update', this._awarenessUpdateHandler); + this.doc.off('update', this._updateHandler); + super.destroy(); + } + + connectBc() { + if (this.disableBc) { + return; + } + if (!this.bcconnected) { + bc.subscribe(this.bcChannel, this._bcSubscriber); + this.bcconnected = true; + } + // send sync step1 to bc + // write sync step 1 + const encoderSync = encoding.createEncoder(); + encoding.writeVarUint(encoderSync, messageSync); + syncProtocol.writeSyncStep1(encoderSync, this.doc); + bc.publish(this.bcChannel, encoding.toUint8Array(encoderSync), this); + // broadcast local state + const encoderState = encoding.createEncoder(); + encoding.writeVarUint(encoderState, messageSync); + syncProtocol.writeSyncStep2(encoderState, this.doc); + bc.publish(this.bcChannel, encoding.toUint8Array(encoderState), this); + // write queryAwareness + const encoderAwarenessQuery = encoding.createEncoder(); + encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness); + bc.publish( + this.bcChannel, + encoding.toUint8Array(encoderAwarenessQuery), + this + ); + // broadcast local awareness state + const encoderAwarenessState = encoding.createEncoder(); + encoding.writeVarUint(encoderAwarenessState, messageAwareness); + encoding.writeVarUint8Array( + encoderAwarenessState, + awarenessProtocol.encodeAwarenessUpdate(this.awareness, [ + this.doc.clientID, + ]) + ); + bc.publish( + this.bcChannel, + encoding.toUint8Array(encoderAwarenessState), + this + ); + } + + disconnectBc() { + // broadcast message with local awareness state set to null (indicating disconnect) + const encoder = encoding.createEncoder(); + encoding.writeVarUint(encoder, messageAwareness); + encoding.writeVarUint8Array( + encoder, + awarenessProtocol.encodeAwarenessUpdate( + this.awareness, + [this.doc.clientID], + new Map() + ) + ); + broadcastMessage(this, encoding.toUint8Array(encoder)); + if (this.bcconnected) { + bc.unsubscribe(this.bcChannel, this._bcSubscriber); + this.bcconnected = false; + } + } + + disconnect() { + this.shouldConnect = false; + this.disconnectBc(); + if (this.ws !== null) { + this.ws.close(); + } + } + + connect() { + this.shouldConnect = true; + if (!this.wsconnected && this.ws === null) { + setupWS(this); + this.connectBc(); + } + } +} diff --git a/packages/data-services/tests/datacenter.spec.ts b/packages/data-services/tests/datacenter.spec.ts new file mode 100644 index 0000000000..d5a4a267ef --- /dev/null +++ b/packages/data-services/tests/datacenter.spec.ts @@ -0,0 +1,8 @@ +import { test, expect } from '@playwright/test'; +import { getDataCenter } from './utils.js'; + +test('can init data center', async () => { + const dataCenter = await getDataCenter(); + + expect(dataCenter).toBeTruthy(); +}); diff --git a/packages/data-services/tests/utils.ts b/packages/data-services/tests/utils.ts new file mode 100644 index 0000000000..c06d906058 --- /dev/null +++ b/packages/data-services/tests/utils.ts @@ -0,0 +1,4 @@ +export const getDataCenter = () => + import('../src/data-center.js').then(async dataCenter => + dataCenter.getDataCenter() + ); diff --git a/packages/data-services/tsconfig.json b/packages/data-services/tsconfig.json new file mode 100644 index 0000000000..063354ba92 --- /dev/null +++ b/packages/data-services/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": false, + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "experimentalDecorators": true, + "declaration": true, + "baseUrl": ".", + "rootDir": ".", + "outDir": "./dist" + }, + "include": ["next-env.d.ts", "src/**/*.ts", "pages/**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/logger/package.json b/packages/logger/package.json index e7656bbf67..43f2a05545 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -1,6 +1,6 @@ { - "name": "@pathfinder/logger", - "version": "0.0.1", + "name": "@affine/logger", + "version": "0.3.0", "description": "", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", @@ -15,7 +15,7 @@ "keywords": [], "repository": { "type": "git", - "url": "git+https://github.com/toeverything/AFFINE.git" + "url": "git+https://github.com/toeverything/AFFiNE.git" }, "dependencies": { "next": "^12.3.1", diff --git a/packages/logger/src/Logger.tsx b/packages/logger/src/Logger.tsx index 21db5d263c..1c2941b38a 100644 --- a/packages/logger/src/Logger.tsx +++ b/packages/logger/src/Logger.tsx @@ -2,7 +2,7 @@ import { useEffect } from 'react'; export const Logger = () => { useEffect(() => { - console.log('@pathfinder/logger: Render Track'); + console.log('@affine/logger: Render Track'); }, []); return null; diff --git a/playwright.config.ts b/playwright.config.ts index b7338d5283..d5a8721f60 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,5 +1,5 @@ import type { PlaywrightTestConfig } from '@playwright/test'; -import { devices } from '@playwright/test'; +// import { devices } from '@playwright/test'; /** * Read environment variables from file. @@ -12,98 +12,29 @@ import { devices } from '@playwright/test'; */ const config: PlaywrightTestConfig = { testDir: './tests', - /* Maximum time one test can run for. */ timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000, - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'list', - // reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + browserName: 'chromium', + viewport: { width: 1440, height: 800 }, + actionTimeout: 5 * 1000, + // Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer + // You can open traces locally(`npx playwright show-trace trace.zip`) + // or in your browser on [Playwright Trace Viewer](https://trace.playwright.dev/). trace: 'on-first-retry', - /* Record video when failure */ - // video: 'retain-on-failure', + // Record video only when retrying a test for the first time. + video: 'on-first-retry', }, - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? '100%' : undefined, - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { - // ...devices['Pixel 5'], - // }, - // }, - // { - // name: 'Mobile Safari', - // use: { - // ...devices['iPhone 12'], - // }, - // }, - - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { - // channel: 'msedge', - // }, - // }, - // { - // name: 'Google Chrome', - // use: { - // channel: 'chrome', - // }, - // }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ webServer: { - command: 'npm run dev', - port: 3000, + command: 'pnpm build && pnpm start -p 8080', + port: 8080, + timeout: 120 * 1000, + reuseExistingServer: !process.env.CI, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 462a551b53..08c3fd7ce6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,37 +4,51 @@ importers: .: specifiers: + '@changesets/cli': ^2.26.0 '@jest/globals': ^29.3.1 - '@playwright/test': ^1.28.1 - '@types/node': 18.7.18 - eslint: 8.22.0 + '@playwright/test': ^1.29.1 + '@types/eslint': ^8.4.10 + '@types/node': ^18.11.17 + '@typescript-eslint/eslint-plugin': ^5.47.0 + '@typescript-eslint/parser': ^5.47.0 + eslint: ^8.30.0 eslint-config-next: 12.3.1 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 got: ^12.5.3 + husky: ^8.0.2 jest: ^29.3.1 + lint-staged: ^13.1.0 prettier: ^2.7.1 ts-jest: ^29.0.3 typescript: ^4.9.3 devDependencies: + '@changesets/cli': 2.26.0 '@jest/globals': 29.3.1 - '@playwright/test': 1.28.1 - '@types/node': 18.7.18 - eslint: 8.22.0 - eslint-config-next: 12.3.1_tg6quxtr5dyl3tikvj7rwonxxi - eslint-config-prettier: 8.5.0_eslint@8.22.0 - eslint-plugin-prettier: 4.2.1_i2cojdczqdiurzgttlwdgf764e + '@playwright/test': 1.29.1 + '@types/eslint': 8.4.10 + '@types/node': 18.11.17 + '@typescript-eslint/eslint-plugin': 5.47.0_dvmqlqg4r5ba7a4i47pzcunkky + '@typescript-eslint/parser': 5.47.0_f5jcydormhcqaoeadqwgqigppy + eslint: 8.30.0 + eslint-config-next: 12.3.1_f5jcydormhcqaoeadqwgqigppy + eslint-config-prettier: 8.5.0_eslint@8.30.0 + eslint-plugin-prettier: 4.2.1_bat6r7ilbbslsp5dhd45s4onze got: 12.5.3 - jest: 29.3.1_@types+node@18.7.18 + husky: 8.0.2 + jest: 29.3.1_@types+node@18.11.17 + lint-staged: 13.1.0 prettier: 2.7.1 ts-jest: 29.0.3_4f6uxrzmuwipl5rr3bcogf6k74 typescript: 4.9.3 packages/app: specifiers: - '@blocksuite/blocks': 0.3.0-alpha.4 - '@blocksuite/editor': 0.3.0-alpha.4 - '@blocksuite/store': 0.3.0-alpha.4 + '@affine/data-services': workspace:* + '@blocksuite/blocks': 0.3.0-20221230100352-5dfe65e + '@blocksuite/editor': 0.3.0-20221230100352-5dfe65e + '@blocksuite/icons': ^2.0.2 + '@blocksuite/store': 0.3.0-20221230100352-5dfe65e '@emotion/css': ^11.10.0 '@emotion/react': ^11.10.4 '@emotion/server': ^11.10.0 @@ -44,27 +58,38 @@ importers: '@mui/base': ^5.0.0-alpha.87 '@mui/icons-material': ^5.10.9 '@mui/material': ^5.8.6 - '@toeverything/pathfinder-logger': workspace:@pathfinder/logger@* + '@toeverything/pathfinder-logger': workspace:@affine/logger@* '@types/node': 18.7.18 '@types/react': 18.0.20 '@types/react-dom': 18.0.6 + '@types/wicg-file-system-access': ^2020.9.5 + chalk-next: ^6.1.5 + cmdk: ^0.1.20 css-spring: ^4.1.0 + dayjs: ^1.11.7 eslint: 8.22.0 eslint-config-next: 12.3.1 eslint-config-prettier: ^8.5.0 eslint-plugin-prettier: ^4.2.1 + i18next: ^21.9.1 lit: ^2.3.1 - next: 13.0.1 + next: 13.1.0 + next-debug-local: ^0.1.5 prettier: ^2.7.1 quill: ^1.3.7 quill-cursors: ^4.0.0 + raw-loader: ^4.0.2 react: 18.2.0 react-dom: 18.2.0 + react-i18next: ^11.18.4 typescript: 4.8.3 + yjs: ^13.5.43 dependencies: - '@blocksuite/blocks': 0.3.0-alpha.4 - '@blocksuite/editor': 0.3.0-alpha.4 - '@blocksuite/store': 0.3.0-alpha.4 + '@affine/data-services': link:../data-services + '@blocksuite/blocks': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 + '@blocksuite/editor': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 + '@blocksuite/icons': 2.0.2_w5j4k42lgipnm43s3brx6h3c34 + '@blocksuite/store': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 '@emotion/css': 11.10.0 '@emotion/react': 11.10.4_w5j4k42lgipnm43s3brx6h3c34 '@emotion/server': 11.10.0_@emotion+css@11.10.0 @@ -75,24 +100,54 @@ importers: '@mui/icons-material': 5.10.9_5fncb4nagb4cvvcnwamw2rozfa '@mui/material': 5.10.9_af5ln35zuaotaffazii6n6bke4 '@toeverything/pathfinder-logger': link:../logger + cmdk: 0.1.20_7ey2zzynotv32rpkwno45fsx4e css-spring: 4.1.0 + dayjs: 1.11.7 + i18next: 21.10.0 lit: 2.4.0 - next: 13.0.1_biqbaboplfbrettd7655fr4n2y + next: 13.1.0_biqbaboplfbrettd7655fr4n2y + next-debug-local: 0.1.5 prettier: 2.7.1 quill: 1.3.7 quill-cursors: 4.0.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 + react-i18next: 11.18.6_vfm63zmruocgezzfl2v26zlzpy + yjs: 13.5.43 devDependencies: '@types/node': 18.7.18 '@types/react': 18.0.20 '@types/react-dom': 18.0.6 + '@types/wicg-file-system-access': 2020.9.5 + chalk-next: 6.1.5 eslint: 8.22.0 eslint-config-next: 12.3.1_76twfck5d7crjqrmw4yltga7zm eslint-config-prettier: 8.5.0_eslint@8.22.0 eslint-plugin-prettier: 4.2.1_i2cojdczqdiurzgttlwdgf764e + raw-loader: 4.0.2 typescript: 4.8.3 + packages/data-services: + specifiers: + '@playwright/test': ^1.29.1 + encoding: ^0.1.13 + firebase: ^9.13.0 + ky: ^0.33.0 + lib0: ^0.2.58 + swr: ^2.0.0 + typescript: ^4.8.4 + y-protocols: ^1.0.5 + dependencies: + encoding: 0.1.13 + firebase: 9.13.0_encoding@0.1.13 + ky: 0.33.0 + lib0: 0.2.58 + swr: 2.0.0 + y-protocols: 1.0.5 + devDependencies: + '@playwright/test': 1.29.1 + typescript: 4.9.3 + packages/logger: specifiers: '@types/react': ^18.0.21 @@ -426,6 +481,13 @@ packages: dependencies: regenerator-runtime: 0.13.9 + /@babel/runtime/7.20.7: + resolution: {integrity: sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: true + /@babel/template/7.18.10: resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==} engines: {node: '>=6.9.0'} @@ -474,51 +536,255 @@ packages: resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} dev: true - /@blocksuite/blocks/0.3.0-alpha.4: - resolution: {integrity: sha512-ij/kKW5ahJQPKMZe2UjwJwvcPNQFQ09/KoRIfFRwBdTBisPGtlSUwunDMlFFoY7CbnH4E+cpg+6XjcJDQniDBw==} + /@blocksuite/blocks/0.3.0-20221230100352-5dfe65e_yjs@13.5.43: + resolution: {integrity: sha512-dvjSbuedu1a5+q+FxtPMt6IDrJT46WsEcT7PhFvnGxP69I5iJwuHzrsDlg159u9Sn6/3OJjE7mYxBodPSGXxBQ==} dependencies: - '@blocksuite/store': 0.3.0-alpha.4 - hotkeys-js: 3.10.0 - lit: 2.4.0 + '@blocksuite/store': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 + '@tldraw/intersect': 1.8.0 + autosize: 5.0.2 + highlight.js: 11.7.0 + hotkeys-js: 3.10.1 + lit: 2.5.0 + perfect-freehand: 1.2.0 quill: 1.3.7 quill-cursors: 4.0.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + - yjs dev: false - /@blocksuite/editor/0.3.0-alpha.4: - resolution: {integrity: sha512-qTNFFIhqQqGT/qwQvUVmgv+EgiBnHB4G5NCPCKn5pdHXM3vrihsY3yG33YVFc1M7+ZwJsfw6sNbtkv+Ih62aaw==} + /@blocksuite/editor/0.3.0-20221230100352-5dfe65e_yjs@13.5.43: + resolution: {integrity: sha512-LMl7jm5cq2/pDmtkLUOIhOwkTm5dDtEYcEKehRl05C6Hs2tHJjDeLwSyL2rXbSCdK8tK2D7tRfnTocBrsom5sw==} dependencies: - '@blocksuite/blocks': 0.3.0-alpha.4 - '@blocksuite/store': 0.3.0-alpha.4 - lit: 2.4.0 - marked: 4.1.1 + '@blocksuite/blocks': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 + '@blocksuite/store': 0.3.0-20221230100352-5dfe65e_yjs@13.5.43 + lit: 2.5.0 + marked: 4.2.5 turndown: 7.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate + - yjs dev: false - /@blocksuite/store/0.3.0-alpha.4: - resolution: {integrity: sha512-hLXFiTf1e4qdX9T0uU+SsBwYWMLqU3ILTxALHRWCxSgKs4/qw7pu24c/XT9L+AAUWFMbNH4MnAK+lrCBiOS1wg==} + /@blocksuite/icons/2.0.2_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-NuPDSKmj3GFZEVSbWuHTtlfwDRPqgbaj/tJTd6VnLsV7wEVjB8Roo5Kqdq9G69Uul2Vc00cpwe4taNhusI/9Ww==} + peerDependencies: + '@types/react': ^18.0.25 + react: ^18.2.0 dependencies: + '@types/react': 18.0.20 + react: 18.2.0 + dev: false + + /@blocksuite/store/0.3.0-20221230100352-5dfe65e_yjs@13.5.43: + resolution: {integrity: sha512-KHLmPqi789kBgkITgSztr6gA5crdEiVDN9Jlslg6Sd7QuFn0jKGFn1FHBn6Myp0nXLAPAa6avBPxlbrgHI+zIQ==} + peerDependencies: + yjs: ^13 + dependencies: + '@types/flexsearch': 0.7.3 + '@types/quill': 1.3.10 + buffer: 6.0.3 flexsearch: 0.7.21 idb-keyval: 6.2.0 - lib0: 0.2.52 - y-indexeddb: 9.0.9_yjs@13.5.41 + ky: 0.33.0 + lib0: 0.2.58 y-protocols: 1.0.5 y-webrtc: 10.2.3 - y-websocket: 1.4.5_yjs@13.5.41 - yjs: 13.5.41 + yjs: 13.5.43 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate dev: false + /@changesets/apply-release-plan/6.1.3: + resolution: {integrity: sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/config': 2.3.0 + '@changesets/get-version-range-type': 0.3.2 + '@changesets/git': 2.0.0 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.7.1 + resolve-from: 5.0.0 + semver: 5.7.1 + dev: true + + /@changesets/assemble-release-plan/5.2.3: + resolution: {integrity: sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.5 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + semver: 5.7.1 + dev: true + + /@changesets/changelog-git/0.1.14: + resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} + dependencies: + '@changesets/types': 5.2.1 + dev: true + + /@changesets/cli/2.26.0: + resolution: {integrity: sha512-0cbTiDms+ICTVtEwAFLNW0jBNex9f5+fFv3I771nBvdnV/mOjd1QJ4+f8KtVSOrwD9SJkk9xbDkWFb0oXd8d1Q==} + hasBin: true + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/apply-release-plan': 6.1.3 + '@changesets/assemble-release-plan': 5.2.3 + '@changesets/changelog-git': 0.1.14 + '@changesets/config': 2.3.0 + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.5 + '@changesets/get-release-plan': 3.0.16 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@changesets/write': 0.2.3 + '@manypkg/get-packages': 1.1.3 + '@types/is-ci': 3.0.0 + '@types/semver': 6.2.3 + ansi-colors: 4.1.3 + chalk: 2.4.2 + enquirer: 2.3.6 + external-editor: 3.1.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + is-ci: 3.0.1 + meow: 6.1.1 + outdent: 0.5.0 + p-limit: 2.3.0 + preferred-pm: 3.0.3 + resolve-from: 5.0.0 + semver: 5.7.1 + spawndamnit: 2.0.0 + term-size: 2.2.1 + tty-table: 4.1.6 + dev: true + + /@changesets/config/2.3.0: + resolution: {integrity: sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ==} + dependencies: + '@changesets/errors': 0.1.4 + '@changesets/get-dependents-graph': 1.3.5 + '@changesets/logger': 0.0.5 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.5 + dev: true + + /@changesets/errors/0.1.4: + resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + dependencies: + extendable-error: 0.1.7 + dev: true + + /@changesets/get-dependents-graph/1.3.5: + resolution: {integrity: sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA==} + dependencies: + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + chalk: 2.4.2 + fs-extra: 7.0.1 + semver: 5.7.1 + dev: true + + /@changesets/get-release-plan/3.0.16: + resolution: {integrity: sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/assemble-release-plan': 5.2.3 + '@changesets/config': 2.3.0 + '@changesets/pre': 1.0.14 + '@changesets/read': 0.5.9 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + dev: true + + /@changesets/get-version-range-type/0.3.2: + resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} + dev: true + + /@changesets/git/2.0.0: + resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.5 + spawndamnit: 2.0.0 + dev: true + + /@changesets/logger/0.0.5: + resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} + dependencies: + chalk: 2.4.2 + dev: true + + /@changesets/parse/0.3.16: + resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} + dependencies: + '@changesets/types': 5.2.1 + js-yaml: 3.14.1 + dev: true + + /@changesets/pre/1.0.14: + resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/errors': 0.1.4 + '@changesets/types': 5.2.1 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + dev: true + + /@changesets/read/0.5.9: + resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/git': 2.0.0 + '@changesets/logger': 0.0.5 + '@changesets/parse': 0.3.16 + '@changesets/types': 5.2.1 + chalk: 2.4.2 + fs-extra: 7.0.1 + p-filter: 2.1.0 + dev: true + + /@changesets/types/4.1.0: + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + dev: true + + /@changesets/types/5.2.1: + resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} + dev: true + + /@changesets/write/0.2.3: + resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/types': 5.2.1 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.7.1 + dev: true + /@emotion/babel-plugin/11.10.2: resolution: {integrity: sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==} peerDependencies: @@ -691,6 +957,490 @@ packages: - supports-color dev: true + /@eslint/eslintrc/1.4.0: + resolution: {integrity: sha512-7yfvXy6MWLgWSFsLhz5yH3iQ52St8cdUY6FoGieKkRDVxuxmrNuUetIuu6cmjNWwniUHiWXjxCr5tTXDrbYS5A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + ajv: 6.12.6 + debug: 4.3.4 + espree: 9.4.0 + globals: 13.19.0 + ignore: 5.2.0 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@firebase/analytics-compat/0.1.17_uhwflrzfcfz2g667jhvqg7sd7i: + resolution: {integrity: sha512-36ByEDsH6/3YNuD6yig30s2A/+E1pt333r8SJirUE8+aHYl/DGX0PXplKvJWDGamYYjMwet3Kt4XRrB1NY8mLg==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/analytics': 0.8.4_@firebase+app@0.8.3 + '@firebase/analytics-types': 0.7.1 + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/analytics-types/0.7.1: + resolution: {integrity: sha512-a1INLjelc1Mqrt2CbGmGdlNBj0zsvwBv0K5q5C6Fje8GSXBMc3+iQQQjzYe/4KkK6nL54UP7ZMeI/Q3VEW72FA==} + dev: false + + /@firebase/analytics/0.8.4_@firebase+app@0.8.3: + resolution: {integrity: sha512-Bgr2tMexv0YrL6kjrOF1xVRts8PM6WWmROpfRQjh0xFU4QSoofBJhkVn2NXDXkHWrr5slFfqB5yOnmgAIsHiMw==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/app-check-compat/0.2.16_uhwflrzfcfz2g667jhvqg7sd7i: + resolution: {integrity: sha512-XxG0gag6In1JPFdANdzhD3neYrXoNTmvSE1+c8PWjnwqBfaNooP6mrwrzIyNTRHderWCnjRlUWvaAKnsprC2Jg==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-check': 0.5.16_@firebase+app@0.8.3 + '@firebase/app-check-types': 0.4.1 + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/app-check-interop-types/0.1.1: + resolution: {integrity: sha512-QpYh5GmiLA9ob8NWAZpHbNNl9TzxxZI4NLevT6MYPRDXKG9BSmBI7FATRfm5uv2QQUVSQrESKog5CCmU16v+7Q==} + dev: false + + /@firebase/app-check-types/0.4.1: + resolution: {integrity: sha512-4X79w2X0H5i5qvaho3qkjZg5qdERnKR4gCfy/fxDmdMMP4QgNJHJ9IBk1E+c4cm5HlaZVcLq9K6z8xaRqjZhyw==} + dev: false + + /@firebase/app-check/0.5.16_@firebase+app@0.8.3: + resolution: {integrity: sha512-dW5b/wlrqDKrdRcIQwnWNzrEK2kH1k+wwy21qOnGnHstrKPfxjgz+HAgTptQLJq1yLjoorksNOq8uie0EsWDrQ==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/app-compat/0.1.38: + resolution: {integrity: sha512-vw+Hu69cNSOgcATV7MH5tBkCxDzjD/M9CROW5n5F58c3K2Lhnhil0T7IbfFhr475kONeD5NKkdRGUqan86pSTg==} + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/app-types/0.8.1: + resolution: {integrity: sha512-p75Ow3QhB82kpMzmOntv866wH9eZ3b4+QbUY+8/DA5Zzdf1c8Nsk8B7kbFpzJt4wwHMdy5LTF5YUnoTc1JiWkw==} + dev: false + + /@firebase/app/0.8.3: + resolution: {integrity: sha512-+YT+DX4m1VAZY6JE5gMt+Xz7O34Y01pqUqoaVO6nbzuohgdcQ57YBL3T26tYqg5JapuwU7CNkFTZOvRc3pYi6g==} + dependencies: + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + idb: 7.0.1 + tslib: 2.4.0 + dev: false + + /@firebase/auth-compat/0.2.24_6e25ipnw6ok3e747dgryxrjf7m: + resolution: {integrity: sha512-IuZQScjtoOLkUHtmIUJ2F3E2OpDOyap6L/9HL/DX3nzEA1LrX7wlpeU6OF2jS9E0KLueWKIrSkIQOOsKoQj/sA==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/auth': 0.20.11_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/auth-types': 0.11.1_ng4u4jtoxpyfdkxsmbmyeoj754 + '@firebase/component': 0.5.21 + '@firebase/util': 1.7.3 + node-fetch: 2.6.7_encoding@0.1.13 + selenium-webdriver: 4.5.0 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - bufferutil + - encoding + - utf-8-validate + dev: false + + /@firebase/auth-interop-types/0.1.7_ng4u4jtoxpyfdkxsmbmyeoj754: + resolution: {integrity: sha512-yA/dTveGGPcc85JP8ZE/KZqfGQyQTBCV10THdI8HTlP1GDvNrhr//J5jAt58MlsCOaO3XmC4DqScPBbtIsR/EA==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.8.1 + '@firebase/util': 1.7.3 + dev: false + + /@firebase/auth-types/0.11.1_ng4u4jtoxpyfdkxsmbmyeoj754: + resolution: {integrity: sha512-ud7T39VG9ptTrC2fOy/XlU+ubC+BVuBJPteuzsPZSa9l7gkntvWgVb3Z/3FxqqRPlkVUYiyvmsbRN3DE1He2ow==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.8.1 + '@firebase/util': 1.7.3 + dev: false + + /@firebase/auth/0.20.11_fg26na3iiuycy3cwzcyxqwfb2e: + resolution: {integrity: sha512-cKy91l4URDG3yWfPK7tjUySh2wCLxtTilsR59jiqQJLReBrQsKP79eFDJ6jqWwbEh3+f1lmoH1nKswwbo9XdmA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + node-fetch: 2.6.7_encoding@0.1.13 + selenium-webdriver: 4.5.0 + tslib: 2.4.0 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: false + + /@firebase/component/0.5.21: + resolution: {integrity: sha512-12MMQ/ulfygKpEJpseYMR0HunJdlsLrwx2XcEs40M18jocy2+spyzHHEwegN3x/2/BLFBjR5247Etmz0G97Qpg==} + dependencies: + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/database-compat/0.2.10_@firebase+app-types@0.8.1: + resolution: {integrity: sha512-fK+IgUUqVKcWK/gltzDU+B1xauCOfY6vulO8lxoNTkcCGlSxuTtwsdqjGkFmgFRMYjXFWWJ6iFcJ/vXahzwCtA==} + dependencies: + '@firebase/component': 0.5.21 + '@firebase/database': 0.13.10_@firebase+app-types@0.8.1 + '@firebase/database-types': 0.9.17 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app-types' + dev: false + + /@firebase/database-types/0.9.17: + resolution: {integrity: sha512-YQm2tCZyxNtEnlS5qo5gd2PAYgKCy69tUKwioGhApCFThW+mIgZs7IeYeJo2M51i4LCixYUl+CvnOyAnb/c3XA==} + dependencies: + '@firebase/app-types': 0.8.1 + '@firebase/util': 1.7.3 + dev: false + + /@firebase/database/0.13.10_@firebase+app-types@0.8.1: + resolution: {integrity: sha512-KRucuzZ7ZHQsRdGEmhxId5jyM2yKsjsQWF9yv0dIhlxYg0D8rCVDZc/waoPKA5oV3/SEIoptF8F7R1Vfe7BCQA==} + dependencies: + '@firebase/auth-interop-types': 0.1.7_ng4u4jtoxpyfdkxsmbmyeoj754 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + faye-websocket: 0.11.4 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app-types' + dev: false + + /@firebase/firestore-compat/0.2.2_6e25ipnw6ok3e747dgryxrjf7m: + resolution: {integrity: sha512-QKgQjyKxx9PxbUebDlKsUinA4Yyvs3m+dH8fJFW1RnQ0SbufCE0vmoJQs+TSZ53Nw8NH2NyVGmXt2aZXiBpnQQ==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/firestore': 3.7.2_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/firestore-types': 2.5.1_ng4u4jtoxpyfdkxsmbmyeoj754 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/firestore-types/2.5.1_ng4u4jtoxpyfdkxsmbmyeoj754: + resolution: {integrity: sha512-xG0CA6EMfYo8YeUxC8FeDzf6W3FX1cLlcAGBYV6Cku12sZRI81oWcu61RSKM66K6kUENP+78Qm8mvroBcm1whw==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.8.1 + '@firebase/util': 1.7.3 + dev: false + + /@firebase/firestore/3.7.2_fg26na3iiuycy3cwzcyxqwfb2e: + resolution: {integrity: sha512-PFTwzgRxCmUKb0jDx5zEAXpl/8dv8+Y0GBMghL9D7kt2ZeIA05yzo05yOKL/d5lShOPJ76/5dkOJty8zg1IBtw==} + engines: {node: '>=10.10.0'} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + '@firebase/webchannel-wrapper': 0.8.1 + '@grpc/grpc-js': 1.7.3 + '@grpc/proto-loader': 0.6.13 + node-fetch: 2.6.7_encoding@0.1.13 + tslib: 2.4.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/functions-compat/0.2.8_6e25ipnw6ok3e747dgryxrjf7m: + resolution: {integrity: sha512-5w668whT+bm6oVcFqIxfFbn9N77WycpNCfZNg1l0iC+5RLSt53RTVu43pqi43vh23Vp4ad+SRBgZiQGAMen5wA==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/functions': 0.8.8_qxaep7pmgqjgshotoaytuprcse + '@firebase/functions-types': 0.5.1 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/functions-types/0.5.1: + resolution: {integrity: sha512-olEJnTuULM/ws0pwhHA0Ze5oIdpFbZsdBGCaBhyL4pm1NUR4Moh0cyAsqr+VtqHCNMGquHU1GJ77qITkoonp0w==} + dev: false + + /@firebase/functions/0.8.8_qxaep7pmgqjgshotoaytuprcse: + resolution: {integrity: sha512-weNcDQJcH3/2YFaXd5dF5pUk3IQdZY60QNuWpq7yS+uaPlCRHjT0K989Q3ZcmYwXz7mHTfhlQamXdA4Yobgt+Q==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/app-check-interop-types': 0.1.1 + '@firebase/auth-interop-types': 0.1.7_ng4u4jtoxpyfdkxsmbmyeoj754 + '@firebase/component': 0.5.21 + '@firebase/messaging-interop-types': 0.1.1 + '@firebase/util': 1.7.3 + node-fetch: 2.6.7_encoding@0.1.13 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/installations-compat/0.1.16_h7lquf2icoxthdkgvez4xhlpaa: + resolution: {integrity: sha512-Xp7s3iUMZ6/TN0a+g1kpHNEn7h59kSxi44/2I7bd3X6xwHnxMu0TqYB7U9WfqEhqiI9iKulL3g06wIZqaklElw==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/installations-types': 0.4.1_@firebase+app-types@0.8.1 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + dev: false + + /@firebase/installations-types/0.4.1_@firebase+app-types@0.8.1: + resolution: {integrity: sha512-ac906QcmipomZjSasGDYNS1LDy4JNGzQ4VXHpFtoOrI6U2QGFkRezZpI+5bzfU062JOD+doO6irYC6Uwnv/GnA==} + peerDependencies: + '@firebase/app-types': 0.x + dependencies: + '@firebase/app-types': 0.8.1 + dev: false + + /@firebase/installations/0.5.16_@firebase+app@0.8.3: + resolution: {integrity: sha512-k3iyjr+yZnDOcJbP+CCZW3/zQJf9gYL2CNBJs9QbmFJoLz7cgIcnAT/XNDMudxcggF1goLfq4+MygpzHD0NzLA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/util': 1.7.3 + idb: 7.0.1 + tslib: 2.4.0 + dev: false + + /@firebase/logger/0.3.4: + resolution: {integrity: sha512-hlFglGRgZEwoyClZcGLx/Wd+zoLfGmbDkFx56mQt/jJ0XMbfPqwId1kiPl0zgdWZX+D8iH+gT6GuLPFsJWgiGw==} + dependencies: + tslib: 2.4.0 + dev: false + + /@firebase/messaging-compat/0.1.20_uhwflrzfcfz2g667jhvqg7sd7i: + resolution: {integrity: sha512-eRKqWetb6VgseMMVJGYqsvl+GMmtSaoVMXOdw3ZqEqeh8rsEaMRyQXpfn5wj+gzTn6JhW2UHdsRUc++qYZcsnw==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/messaging': 0.10.0_@firebase+app@0.8.3 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/messaging-interop-types/0.1.1: + resolution: {integrity: sha512-7XuY87zPh01EBaeS3s6co31Il5oGbPl5MxAg6Uj3fPv7PqJQlbwQ+B5k7CKSF/Y26tRxp+u+usxIvIWCSEA8CQ==} + dev: false + + /@firebase/messaging/0.10.0_@firebase+app@0.8.3: + resolution: {integrity: sha512-1LREZ2IX6kzrne/mAhbUEFRFzTZkhyh6bYjAdUnaYrvHNwm0Y2x88WIJVJmGC7rroxJxbeGZMQ+cL/r9rBAwPA==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/messaging-interop-types': 0.1.1 + '@firebase/util': 1.7.3 + idb: 7.0.1 + tslib: 2.4.0 + dev: false + + /@firebase/performance-compat/0.1.16_uhwflrzfcfz2g667jhvqg7sd7i: + resolution: {integrity: sha512-ViGASnBVZrhoDC879fzdsvmPEFRMBgJ/Y4MuBkkg7Y4/Sd3SAB2IOhVn92oyPdYr8FeIoH7irN66zNnljKsXEg==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/performance': 0.5.16_@firebase+app@0.8.3 + '@firebase/performance-types': 0.1.1 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/performance-types/0.1.1: + resolution: {integrity: sha512-wiJRLBg8EPaYSGJqx7aqkZ3L5fULfZa9zOTs4C06K020g0zzJh9kUUO/0U3wvHz7zRQjJxTO8Jw4SDjxs3EZrA==} + dev: false + + /@firebase/performance/0.5.16_@firebase+app@0.8.3: + resolution: {integrity: sha512-6099wYEh5Fmm9ZHRcl8uUPsWkzbYkpLSZyMfyJKHTseQeOLE5OYPzKgia8fYAHlP2dID3cylyXfZTJo/Eak8wQ==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/remote-config-compat/0.1.16_uhwflrzfcfz2g667jhvqg7sd7i: + resolution: {integrity: sha512-BWonzeqODnGki/fZ17zOnjJFR5CWbIOU0PmYGjWBnbkWxpFDdE3zNsz8JTVd/Mkt7y2PHFMYpLsyZ473E/62FQ==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/logger': 0.3.4 + '@firebase/remote-config': 0.3.15_@firebase+app@0.8.3 + '@firebase/remote-config-types': 0.2.1 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + dev: false + + /@firebase/remote-config-types/0.2.1: + resolution: {integrity: sha512-1PGx4vKtMMd5uB6G1Nj2b8fOnJx7mIJGzkdyfhIM1oQx9k3dJ+pVu4StrNm46vHaD8ZlOQLr91YfUE43xSXwSg==} + dev: false + + /@firebase/remote-config/0.3.15_@firebase+app@0.8.3: + resolution: {integrity: sha512-ZCyqoCaftoNvc2r4zPaqNV4OgC4sRHjcQI+agzXESnhDLnTY8DpCaQ0m9j6deHuxxDOgu8QPDb8psLbjR+9CgQ==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/logger': 0.3.4 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + dev: false + + /@firebase/storage-compat/0.1.21_6e25ipnw6ok3e747dgryxrjf7m: + resolution: {integrity: sha512-/HWdKKykNSY0xcwyAf+Fooq8VK+fjaRc7oRZ7PM+ovGb6rnqINIiRUxVudFOdEvqN5ftiVY0vSKdTYzbmeBr+w==} + peerDependencies: + '@firebase/app-compat': 0.x + dependencies: + '@firebase/app-compat': 0.1.38 + '@firebase/component': 0.5.21 + '@firebase/storage': 0.9.13_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/storage-types': 0.6.1_ng4u4jtoxpyfdkxsmbmyeoj754 + '@firebase/util': 1.7.3 + tslib: 2.4.0 + transitivePeerDependencies: + - '@firebase/app' + - '@firebase/app-types' + - encoding + dev: false + + /@firebase/storage-types/0.6.1_ng4u4jtoxpyfdkxsmbmyeoj754: + resolution: {integrity: sha512-/pkNzKiGCSjdBBZHPvWL1kkPZfM3pFJ38HPJE1xTHwLBwdrFb4JrmY+5/E4ma5ePsbejecIOD1SZhEKDB/JwUQ==} + peerDependencies: + '@firebase/app-types': 0.x + '@firebase/util': 1.x + dependencies: + '@firebase/app-types': 0.8.1 + '@firebase/util': 1.7.3 + dev: false + + /@firebase/storage/0.9.13_fg26na3iiuycy3cwzcyxqwfb2e: + resolution: {integrity: sha512-FZKF35rPidxRc1ZgOYLJt9nEfA30HAa/tZ9IhzVTKmqB4p7P13QCxMQphOMcucbCf84IsuGWu/YwYc/EtvhwRw==} + peerDependencies: + '@firebase/app': 0.x + dependencies: + '@firebase/app': 0.8.3 + '@firebase/component': 0.5.21 + '@firebase/util': 1.7.3 + node-fetch: 2.6.7_encoding@0.1.13 + tslib: 2.4.0 + transitivePeerDependencies: + - encoding + dev: false + + /@firebase/util/1.7.3: + resolution: {integrity: sha512-wxNqWbqokF551WrJ9BIFouU/V5SL1oYCGx1oudcirdhadnQRFH5v1sjgGL7cUV/UsekSycygphdrF2lxBxOYKg==} + dependencies: + tslib: 2.4.0 + dev: false + + /@firebase/webchannel-wrapper/0.8.1: + resolution: {integrity: sha512-CJW8vxt6bJaBeco2VnlJjmCmAkrrtIdf0GGKvpAB4J5gw8Gi0rHb+qsgKp6LsyS5W6ALPLawLs7phZmw02dvLw==} + dev: false + /@fontsource/poppins/4.5.10: resolution: {integrity: sha512-oYZVHsnlwV3VWM1SE/m6SUxVh0QLk5+2wB+lBiXcI7N/0KVQWmV9YykaPqfJHBLvmdya+MAOnv+BQHeLU1vOOw==} dev: false @@ -699,6 +1449,38 @@ packages: resolution: {integrity: sha512-OTrWNdcPp01bZjEbSu52vMu9PaReUFylHAMI4lctKVbYUnm+e7a4eG6YcnRvDrJEMYHBDtEWLAsqGVldV5r1EQ==} dev: false + /@grpc/grpc-js/1.7.3: + resolution: {integrity: sha512-H9l79u4kJ2PVSxUNA08HMYAnUBLj9v6KjYQ7SQ71hOZcEXhShE/y5iQCesP8+6/Ik/7i2O0a10bPquIcYfufog==} + engines: {node: ^8.13.0 || >=10.10.0} + dependencies: + '@grpc/proto-loader': 0.7.3 + '@types/node': 18.11.17 + dev: false + + /@grpc/proto-loader/0.6.13: + resolution: {integrity: sha512-FjxPYDRTn6Ec3V0arm1FtSpmP6V50wuph2yILpyvTKzjc76oDdoihXqM1DzOW5ubvCC8GivfCnNtfaRE8myJ7g==} + engines: {node: '>=6'} + hasBin: true + dependencies: + '@types/long': 4.0.2 + lodash.camelcase: 4.3.0 + long: 4.0.0 + protobufjs: 6.11.3 + yargs: 16.2.0 + dev: false + + /@grpc/proto-loader/0.7.3: + resolution: {integrity: sha512-5dAvoZwna2Py3Ef96Ux9jIkp3iZ62TUsV00p3wVBPNX5K178UbNi8Q7gQVqwXT1Yq9RejIGG9G2IPEo93T6RcA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + '@types/long': 4.0.2 + lodash.camelcase: 4.3.0 + long: 4.0.0 + protobufjs: 7.1.2 + yargs: 16.2.0 + dev: false + /@humanwhocodes/config-array/0.10.4: resolution: {integrity: sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==} engines: {node: '>=10.10.0'} @@ -710,10 +1492,26 @@ packages: - supports-color dev: true + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} + engines: {node: '>=10.10.0'} + dependencies: + '@humanwhocodes/object-schema': 1.2.1 + debug: 4.3.4 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /@humanwhocodes/gitignore-to-minimatch/1.0.2: resolution: {integrity: sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==} dev: true + /@humanwhocodes/module-importer/1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: true + /@humanwhocodes/object-schema/1.2.1: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true @@ -739,7 +1537,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 jest-message-util: 29.3.1 jest-util: 29.3.1 @@ -760,14 +1558,14 @@ packages: '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.0 exit: 0.1.2 graceful-fs: 4.2.10 jest-changed-files: 29.2.0 - jest-config: 29.3.1_@types+node@18.7.18 + jest-config: 29.3.1_@types+node@18.11.17 jest-haste-map: 29.3.1 jest-message-util: 29.3.1 jest-regex-util: 29.2.0 @@ -794,7 +1592,7 @@ packages: dependencies: '@jest/fake-timers': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 jest-mock: 29.3.1 dev: true @@ -821,7 +1619,7 @@ packages: dependencies: '@jest/types': 29.3.1 '@sinonjs/fake-timers': 9.1.2 - '@types/node': 18.7.18 + '@types/node': 18.11.17 jest-message-util: 29.3.1 jest-mock: 29.3.1 jest-util: 29.3.1 @@ -854,7 +1652,7 @@ packages: '@jest/transform': 29.3.1 '@jest/types': 29.3.1 '@jridgewell/trace-mapping': 0.3.17 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -942,7 +1740,7 @@ packages: '@jest/schemas': 29.0.0 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 '@types/yargs': 17.0.15 chalk: 4.1.2 dev: true @@ -989,6 +1787,30 @@ packages: resolution: {integrity: sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw==} dev: false + /@lit/reactive-element/1.5.0: + resolution: {integrity: sha512-fQh9FDK0LPTwDk+0HhSZEtb8K0LTN1wXerwpGrWA+a8tWulYRDLI4vQDWp4GOIsewn0572KYV/oZ3+492D7osA==} + dev: false + + /@manypkg/find-root/1.1.0: + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + dependencies: + '@babel/runtime': 7.20.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + dev: true + + /@manypkg/get-packages/1.1.3: + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + dependencies: + '@babel/runtime': 7.20.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + dev: true + /@mui/base/5.0.0-alpha.101_7ey2zzynotv32rpkwno45fsx4e: resolution: {integrity: sha512-a54BcXvArGOKUZ2zyS/7B9GNhAGgfomEQSkfEZ88Nc9jKvXA+Mppenfz5o4JCAnD8c4VlePmz9rKOYvvum1bZw==} engines: {node: '>=12.0.0'} @@ -1168,8 +1990,8 @@ packages: resolution: {integrity: sha512-9P9THmRFVKGKt9DYqeC2aKIxm8rlvkK38V1P1sRE7qyoPBIs8l9oo79QoSdPtOWfzkbDAVUqvbQGgTMsb8BtJg==} dev: false - /@next/env/13.0.1: - resolution: {integrity: sha512-gK60YoFae3s8qi5UgIzbvxOhsh5gKyEaiKH5+kLBUYXLlrPyWJR2xKBj2WqvHkO7wDX7/Hed3DAqjSpU4ijIvQ==} + /@next/env/13.1.0: + resolution: {integrity: sha512-6iNixFzCndH+Bl4FetQzOMjxCJqg8fs0LAlZviig1K6mIjOWH2m2oPcHcOg1Ta5VCe7Bx5KG1Hs+NrWDUkBt9A==} dev: false /@next/eslint-plugin-next/12.3.1: @@ -1187,8 +2009,8 @@ packages: dev: false optional: true - /@next/swc-android-arm-eabi/13.0.1: - resolution: {integrity: sha512-M28QSbohZlNXNn//HY6lV2T3YaMzG58Jwr0YwOdVmOQv6i+7lu6xe3GqQu4kdqInqhLrBXnL+nabFuGTVSHtTg==} + /@next/swc-android-arm-eabi/13.1.0: + resolution: {integrity: sha512-ANBZZRjZBV+Sii11ZVxbxSvfIi6dZwu4w+XnJBDmz+0/wtAigpjYWyMkuWZ/RCD7INdusOlU4EgJ99WzWGIDjA==} engines: {node: '>= 10'} cpu: [arm] os: [android] @@ -1205,8 +2027,8 @@ packages: dev: false optional: true - /@next/swc-android-arm64/13.0.1: - resolution: {integrity: sha512-szmO/i6GoHcPXcbhUKhwBMETWHNXH3ITz9wfxwOOFBNKdDU8pjKsHL88lg28aOiQYZSU1sxu1v1p9KY5kJIZCg==} + /@next/swc-android-arm64/13.1.0: + resolution: {integrity: sha512-nPwbkS3aZjCIe61wztgjXjIeylijOP8uGtDGjjJVUF3B/5GLVx3ngZu6tjPTMEgaLM0u//HuGK+aZolWUQWE4g==} engines: {node: '>= 10'} cpu: [arm64] os: [android] @@ -1223,8 +2045,8 @@ packages: dev: false optional: true - /@next/swc-darwin-arm64/13.0.1: - resolution: {integrity: sha512-O1RxCaiDNOjGZmdAp6SQoHUITt9aVDQXoR3lZ/TloI/NKRAyAV4u0KUUofK+KaZeHOmVTnPUaQuCyZSc3i1x5Q==} + /@next/swc-darwin-arm64/13.1.0: + resolution: {integrity: sha512-0hUydiAW18jK2uGPnZRdnRQtdB/3ZoPo84A6zH7MJHxAWw9lzVsv3kMg9kgVBBlrivzqdNN8rdgA+eYNxzXU9w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1241,8 +2063,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64/13.0.1: - resolution: {integrity: sha512-8E6BY/VO+QqQkthhoWgB8mJMw1NcN9Vhl2OwEwxv8jy2r3zjeU+WNRxz4y8RLbcY0R1h+vHlXuP0mLnuac84tQ==} + /@next/swc-darwin-x64/13.1.0: + resolution: {integrity: sha512-3S3iQqJIysklj0Q9gnanuYMzF8H9p+fUVhvSHxVVLcKH4HsE8EGddNkXsaOyznL1kC6vGKw7h6uz1ojaXEafCA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1259,8 +2081,8 @@ packages: dev: false optional: true - /@next/swc-freebsd-x64/13.0.1: - resolution: {integrity: sha512-ocwoOxm2KVwF50RyoAT+2RQPLlkyoF7sAqzMUVgj+S6+DTkY3iwH+Zpo0XAk2pnqT9qguOrKnEpq9EIx//+K7Q==} + /@next/swc-freebsd-x64/13.1.0: + resolution: {integrity: sha512-wAgzwm/em48GIuWq3OYr0BpncMy7c+UA3hsyX+xKh/vb/sOIpQly7JTa+GNdk17s7kprhMfsgzPG3da36NLpkA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] @@ -1277,8 +2099,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm-gnueabihf/13.0.1: - resolution: {integrity: sha512-yO7e3zITfGol/N6lPQnmIRi0WyuILBMXrvH6EdmWzzqMDJFfTCII6l+B6gMO5WVDCTQUGQlQRNZ7sFqWR4I71g==} + /@next/swc-linux-arm-gnueabihf/13.1.0: + resolution: {integrity: sha512-Cr2hzL7ad+4nj9KrR1Cz1RDcsWa61X6I7gc6PToRYIY4gL480Sijq19xo7dlXQPnr1viVzbNiNnNXZASHv7uvw==} engines: {node: '>= 10'} cpu: [arm] os: [linux] @@ -1295,8 +2117,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu/13.0.1: - resolution: {integrity: sha512-OEs6WDPDI8RyM8SjOqTDMqMBfOlU97VnW6ZMXUvzUTyH0K9c7NF+cn7UMu+I4tKFN0uJ9WQs/6TYaFBGkgoVVA==} + /@next/swc-linux-arm64-gnu/13.1.0: + resolution: {integrity: sha512-EjCIKfeZB9h72evL2yGNwBvE5Im96Zn7o2zxImlvCiUYb/xXDqn4hzhck035BSP3g3sGDLfijFTE1wKRyXIk4w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1313,8 +2135,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl/13.0.1: - resolution: {integrity: sha512-y5ypFK0Y3urZSFoQxbtDqvKsBx026sz+Fm+xHlPWlGHNZrbs3Q812iONjcZTo09QwRMk5X86iMWBRxV18xMhaw==} + /@next/swc-linux-arm64-musl/13.1.0: + resolution: {integrity: sha512-WAsZtCtPXlz/7/bnW9ryw856xEun+c6xSwZwbcvrMxtcSiW3z0LD91Nsj3AkexsjRtBjeEpNeVtDExqF2VKKSA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1331,8 +2153,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu/13.0.1: - resolution: {integrity: sha512-XDIHEE6SU8VCF+dUVntD6PDv6RK31N0forx9kucZBYirbe8vCZ+Yx8hYgvtIaGrTcWtGxibxmND0pIuHDq8H5g==} + /@next/swc-linux-x64-gnu/13.1.0: + resolution: {integrity: sha512-Tjd5gieI3X9vPce5yF+GsQxOl0jwUkyOrTR1g5PQr+bT/9Qos/yPL48H1L5ayEp0hxgLVPW7skGal7lVnAoVEQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1349,8 +2171,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl/13.0.1: - resolution: {integrity: sha512-yxIOuuz5EOx0F1FDtsyzaLgnDym0Ysxv8CWeJyDTKKmt9BVyITg6q/cD+RP9bEkT1TQi+PYXIMATSz675Q82xw==} + /@next/swc-linux-x64-musl/13.1.0: + resolution: {integrity: sha512-H9UMEQv40e9pkgdX4mCms0dDf2dimmZ6WXhDTWF/yIh9icgcsHaP73BJ9IFlgvh80wLiUgWZ3LAX4vXnXzidmg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1367,8 +2189,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc/13.0.1: - resolution: {integrity: sha512-+ucLe2qgQzP+FM94jD4ns6LDGyMFaX9k3lVHqu/tsQCy2giMymbport4y4p77mYcXEMlDaHMzlHgOQyHRniWFA==} + /@next/swc-win32-arm64-msvc/13.1.0: + resolution: {integrity: sha512-LFFIKjW/cPl4wvG8HF/6oYPJZ+Jy32G3FUflC8UW1Od6W9yOSEvadhk9fMyDZN4cgsNOcVc3uVSMpcuuCpbDGw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1385,8 +2207,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc/13.0.1: - resolution: {integrity: sha512-Krr/qGN7OB35oZuvMAZKoXDt2IapynIWLh5A5rz6AODb7f/ZJqyAuZSK12vOa2zKdobS36Qm4IlxxBqn9c00MA==} + /@next/swc-win32-ia32-msvc/13.1.0: + resolution: {integrity: sha512-MBLaoHZSenMdxhB3Ww1VNEhjyPT3uLjzAi5Ygk48LLLbOGu5KxQolhINRrqGuJWqJRNWSJ9JSFBfJrZwQzrUew==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1403,8 +2225,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc/13.0.1: - resolution: {integrity: sha512-t/0G33t/6VGWZUGCOT7rG42qqvf/x+MrFp1CU+8CN6PrjSSL57R5bqkXfubV9t4eCEnUxVP+5Hn3MoEXEebtEw==} + /@next/swc-win32-x64-msvc/13.1.0: + resolution: {integrity: sha512-fFTfIQvnmpbKoyh4v3ezlGqtERlgc2Sx8qJwPuYqoVi0V08wCx9wp2Iq1CINxP3UMHkEeNX7gYpDOd+9Cw9EiQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -1433,19 +2255,247 @@ packages: fastq: 1.13.0 dev: true - /@playwright/test/1.28.1: - resolution: {integrity: sha512-xN6spdqrNlwSn9KabIhqfZR7IWjPpFK1835tFNgjrlysaSezuX8PYUwaz38V/yI8TJLG9PkAMEXoHRXYXlpTPQ==} + /@playwright/test/1.29.1: + resolution: {integrity: sha512-iQxk2DX5U9wOGV3+/Jh9OHPsw5H3mleUL2S4BgQuwtlAfK3PnKvn38m4Rg9zIViGHVW24opSm99HQm/UFLEy6w==} engines: {node: '>=14'} hasBin: true dependencies: - '@types/node': 18.7.18 - playwright-core: 1.28.1 + '@types/node': 18.11.17 + playwright-core: 1.29.1 dev: true /@popperjs/core/2.11.6: resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} dev: false + /@protobufjs/aspromise/1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64/1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen/2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter/1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch/1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float/1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire/1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path/1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool/1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8/1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + + /@radix-ui/primitive/1.0.0: + resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} + dependencies: + '@babel/runtime': 7.19.0 + dev: false + + /@radix-ui/react-compose-refs/1.0.0_react@18.2.0: + resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-context/1.0.0_react@18.2.0: + resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-dialog/1.0.0_7ey2zzynotv32rpkwno45fsx4e: + resolution: {integrity: sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-context': 1.0.0_react@18.2.0 + '@radix-ui/react-dismissable-layer': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-focus-guards': 1.0.0_react@18.2.0 + '@radix-ui/react-focus-scope': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-id': 1.0.0_react@18.2.0 + '@radix-ui/react-portal': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-slot': 1.0.0_react@18.2.0 + '@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0 + aria-hidden: 1.2.2_w5j4k42lgipnm43s3brx6h3c34 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + react-remove-scroll: 2.5.4_w5j4k42lgipnm43s3brx6h3c34 + transitivePeerDependencies: + - '@types/react' + dev: false + + /@radix-ui/react-dismissable-layer/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + '@radix-ui/react-use-escape-keydown': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-focus-guards/1.0.0_react@18.2.0: + resolution: {integrity: sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-id/1.0.0_react@18.2.0: + resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-portal/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-primitive': 1.0.0_biqbaboplfbrettd7655fr4n2y + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-presence/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + '@radix-ui/react-use-layout-effect': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-primitive/1.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-slot': 1.0.0_react@18.2.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + + /@radix-ui/react-slot/1.0.0_react@18.2.0: + resolution: {integrity: sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-compose-refs': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-callback-ref/1.0.0_react@18.2.0: + resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state/1.0.0_react@18.2.0: + resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown/1.0.0_react@18.2.0: + resolution: {integrity: sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + '@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect/1.0.0_react@18.2.0: + resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + dependencies: + '@babel/runtime': 7.19.0 + react: 18.2.0 + dev: false + /@rushstack/eslint-patch/1.2.0: resolution: {integrity: sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==} dev: true @@ -1477,6 +2527,12 @@ packages: tslib: 2.4.0 dev: false + /@swc/helpers/0.4.14: + resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==} + dependencies: + tslib: 2.4.0 + dev: false + /@szmarczak/http-timer/5.0.1: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1484,6 +2540,16 @@ packages: defer-to-connect: 2.0.1 dev: true + /@tldraw/intersect/1.8.0: + resolution: {integrity: sha512-0UarshNpyq2+O4o0xHMJIBgF0E630mes5CkMoO+D5xgYppSBIkeqYDcv0ujsmAhMKX1O6Y0ShuuHeflBEULUoQ==} + dependencies: + '@tldraw/vec': 1.8.0 + dev: false + + /@tldraw/vec/1.8.0: + resolution: {integrity: sha512-GiS5Df3CzXY/fPBFcM0CKFERZfI4Cg1X33VPZX+NLo7Fwm/h9zu/aU24N1mG75Q9LuMnwKm7woxKr8BiUXGYCg==} + dev: false + /@types/babel__core/7.1.20: resolution: {integrity: sha512-PVb6Bg2QuscZ30FvOU7z4guG6c926D9YRvOxEaelzndpMsvP+YM74Q/dAFASpg2l6+XLalxSGxcq/lrgYWZtyQ==} dependencies: @@ -1513,14 +2579,31 @@ packages: '@babel/types': 7.20.5 dev: true + /@types/eslint/8.4.10: + resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==} + dependencies: + '@types/estree': 1.0.0 + '@types/json-schema': 7.0.11 + dev: true + + /@types/estree/1.0.0: + resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + dev: true + + /@types/flexsearch/0.7.3: + resolution: {integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==} + dev: false + /@types/graceful-fs/4.1.5: resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} dependencies: - '@types/node': 18.7.18 + '@types/node': 18.11.17 dev: true - /@types/http-cache-semantics/4.0.1: - resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} + /@types/is-ci/3.0.0: + resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} + dependencies: + ci-info: 3.7.0 dev: true /@types/istanbul-lib-coverage/2.0.4: @@ -1539,14 +2622,37 @@ packages: '@types/istanbul-lib-report': 3.0.0 dev: true + /@types/json-schema/7.0.11: + resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} + dev: true + /@types/json5/0.0.29: resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} dev: true + /@types/long/4.0.2: + resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==} + dev: false + + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + + /@types/node/12.20.55: + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + dev: true + + /@types/node/18.11.17: + resolution: {integrity: sha512-HJSUJmni4BeDHhfzn6nF0sVmd1SMezP7/4F0Lq+aXzmp2xm9O7WXrUtHW/CHlYVtZUbByEvWidHqRtcJXGF2Ng==} + /@types/node/18.7.18: resolution: {integrity: sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==} dev: true + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + /@types/parse-json/4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: false @@ -1558,6 +2664,12 @@ packages: /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/quill/1.3.10: + resolution: {integrity: sha512-IhW3fPW+bkt9MLNlycw8u8fWb7oO7W5URC9MfZYHBlA24rex9rs23D5DETChu1zvgVdc5ka64ICjJOgQMr6Shw==} + dependencies: + parchment: 1.1.4 + dev: false + /@types/react-dom/18.0.6: resolution: {integrity: sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA==} dependencies: @@ -1594,6 +2706,14 @@ packages: /@types/scheduler/0.16.2: resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + /@types/semver/6.2.3: + resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==} + dev: true + + /@types/semver/7.3.13: + resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} + dev: true + /@types/stack-utils/2.0.1: resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} dev: true @@ -1602,6 +2722,10 @@ packages: resolution: {integrity: sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==} dev: false + /@types/wicg-file-system-access/2020.9.5: + resolution: {integrity: sha512-UYK244awtmcUYQfs7FR8710MJcefL2WvkyHMjA8yJzxd1mo0Gfn88sRZ1Bls7hiUhA2w7ne1gpJ9T5g3G0wOyA==} + dev: true + /@types/yargs-parser/21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true @@ -1612,8 +2736,35 @@ packages: '@types/yargs-parser': 21.0.0 dev: true - /@typescript-eslint/parser/5.38.0_76twfck5d7crjqrmw4yltga7zm: - resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} + /@typescript-eslint/eslint-plugin/5.47.0_dvmqlqg4r5ba7a4i47pzcunkky: + resolution: {integrity: sha512-AHZtlXAMGkDmyLuLZsRpH3p4G/1iARIwc/T0vIem2YB+xW6pZaXYXzCBnZSF/5fdM97R9QqZWZ+h3iW10XgevQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + '@typescript-eslint/parser': ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/parser': 5.47.0_f5jcydormhcqaoeadqwgqigppy + '@typescript-eslint/scope-manager': 5.47.0 + '@typescript-eslint/type-utils': 5.47.0_f5jcydormhcqaoeadqwgqigppy + '@typescript-eslint/utils': 5.47.0_f5jcydormhcqaoeadqwgqigppy + debug: 4.3.4 + eslint: 8.30.0 + ignore: 5.2.0 + natural-compare-lite: 1.4.0 + regexpp: 3.2.0 + semver: 7.3.7 + tsutils: 3.21.0_typescript@4.9.3 + typescript: 4.9.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.47.0_76twfck5d7crjqrmw4yltga7zm: + resolution: {integrity: sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1622,9 +2773,9 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.38.0 - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.8.3 + '@typescript-eslint/scope-manager': 5.47.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/typescript-estree': 5.47.0_typescript@4.8.3 debug: 4.3.4 eslint: 8.22.0 typescript: 4.8.3 @@ -1632,8 +2783,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.38.0_tg6quxtr5dyl3tikvj7rwonxxi: - resolution: {integrity: sha512-/F63giJGLDr0ms1Cr8utDAxP2SPiglaD6V+pCOcG35P2jCqdfR7uuEhz1GIC3oy4hkUF8xA1XSXmd9hOh/a5EA==} + /@typescript-eslint/parser/5.47.0_f5jcydormhcqaoeadqwgqigppy: + resolution: {integrity: sha512-udPU4ckK+R1JWCGdQC4Qa27NtBg7w020ffHqGyAK8pAgOVuNw7YaKXGChk+udh+iiGIJf6/E/0xhVXyPAbsczw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -1642,31 +2793,51 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.38.0 - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/typescript-estree': 5.38.0_typescript@4.9.3 + '@typescript-eslint/scope-manager': 5.47.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/typescript-estree': 5.47.0_typescript@4.9.3 debug: 4.3.4 - eslint: 8.22.0 + eslint: 8.30.0 typescript: 4.9.3 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.38.0: - resolution: {integrity: sha512-ByhHIuNyKD9giwkkLqzezZ9y5bALW8VNY6xXcP+VxoH4JBDKjU5WNnsiD4HJdglHECdV+lyaxhvQjTUbRboiTA==} + /@typescript-eslint/scope-manager/5.47.0: + resolution: {integrity: sha512-dvJab4bFf7JVvjPuh3sfBUWsiD73aiftKBpWSfi3sUkysDQ4W8x+ZcFpNp7Kgv0weldhpmMOZBjx1wKN8uWvAw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/visitor-keys': 5.38.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/visitor-keys': 5.47.0 dev: true - /@typescript-eslint/types/5.38.0: - resolution: {integrity: sha512-HHu4yMjJ7i3Cb+8NUuRCdOGu2VMkfmKyIJsOr9PfkBVYLYrtMCK/Ap50Rpov+iKpxDTfnqvDbuPLgBE5FwUNfA==} + /@typescript-eslint/type-utils/5.47.0_f5jcydormhcqaoeadqwgqigppy: + resolution: {integrity: sha512-1J+DFFrYoDUXQE1b7QjrNGARZE6uVhBqIvdaXTe5IN+NmEyD68qXR1qX1g2u4voA+nCaelQyG8w30SAOihhEYg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: '*' + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/typescript-estree': 5.47.0_typescript@4.9.3 + '@typescript-eslint/utils': 5.47.0_f5jcydormhcqaoeadqwgqigppy + debug: 4.3.4 + eslint: 8.30.0 + tsutils: 3.21.0_typescript@4.9.3 + typescript: 4.9.3 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/types/5.47.0: + resolution: {integrity: sha512-eslFG0Qy8wpGzDdYKu58CEr3WLkjwC5Usa6XbuV89ce/yN5RITLe1O8e+WFEuxnfftHiJImkkOBADj58ahRxSg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.38.0_typescript@4.8.3: - resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==} + /@typescript-eslint/typescript-estree/5.47.0_typescript@4.8.3: + resolution: {integrity: sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1674,8 +2845,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/visitor-keys': 5.38.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/visitor-keys': 5.47.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1686,8 +2857,8 @@ packages: - supports-color dev: true - /@typescript-eslint/typescript-estree/5.38.0_typescript@4.9.3: - resolution: {integrity: sha512-6P0RuphkR+UuV7Avv7MU3hFoWaGcrgOdi8eTe1NwhMp2/GjUJoODBTRWzlHpZh6lFOaPmSvgxGlROa0Sg5Zbyg==} + /@typescript-eslint/typescript-estree/5.47.0_typescript@4.9.3: + resolution: {integrity: sha512-LxfKCG4bsRGq60Sqqu+34QT5qT2TEAHvSCCJ321uBWywgE2dS0LKcu5u+3sMGo+Vy9UmLOhdTw5JHzePV/1y4Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -1695,8 +2866,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.38.0 - '@typescript-eslint/visitor-keys': 5.38.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/visitor-keys': 5.47.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -1707,37 +2878,33 @@ packages: - supports-color dev: true - /@typescript-eslint/visitor-keys/5.38.0: - resolution: {integrity: sha512-MxnrdIyArnTi+XyFLR+kt/uNAcdOnmT+879os7qDRI+EYySR4crXJq9BXPfRzzLGq0wgxkwidrCJ9WCAoacm1w==} + /@typescript-eslint/utils/5.47.0_f5jcydormhcqaoeadqwgqigppy: + resolution: {integrity: sha512-U9xcc0N7xINrCdGVPwABjbAKqx4GK67xuMV87toI+HUqgXj26m6RBp9UshEXcTrgCkdGYFzgKLt8kxu49RilDw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@typescript-eslint/types': 5.38.0 - eslint-visitor-keys: 3.3.0 + '@types/json-schema': 7.0.11 + '@types/semver': 7.3.13 + '@typescript-eslint/scope-manager': 5.47.0 + '@typescript-eslint/types': 5.47.0 + '@typescript-eslint/typescript-estree': 5.47.0_typescript@4.9.3 + eslint: 8.30.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.30.0 + semver: 7.3.7 + transitivePeerDependencies: + - supports-color + - typescript dev: true - /abstract-leveldown/6.2.3: - resolution: {integrity: sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==} - engines: {node: '>=6'} + /@typescript-eslint/visitor-keys/5.47.0: + resolution: {integrity: sha512-ByPi5iMa6QqDXe/GmT/hR6MZtVPi0SqMQPDx15FczCBXJo/7M8T88xReOALAfpBLm+zxpPfmhuEvPb577JRAEg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - buffer: 5.7.1 - immediate: 3.3.0 - level-concat-iterator: 2.0.1 - level-supports: 1.0.1 - xtend: 4.0.2 - dev: false - optional: true - - /abstract-leveldown/6.3.0: - resolution: {integrity: sha512-TU5nlYgta8YrBMNpc9FwQzRbiXsj49gsALsXadbGHt9CROPzX5fB0rWDR5mtdpOOKa5XqRFpbj1QroPAoPzVjQ==} - engines: {node: '>=6'} - dependencies: - buffer: 5.7.1 - immediate: 3.3.0 - level-concat-iterator: 2.0.1 - level-supports: 1.0.1 - xtend: 4.0.2 - dev: false - optional: true + '@typescript-eslint/types': 5.47.0 + eslint-visitor-keys: 3.3.0 + dev: true /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -1752,6 +2919,22 @@ packages: engines: {node: '>=0.4.0'} dev: true + /aggregate-error/3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + dev: true + + /ajv-keywords/3.5.2_ajv@6.12.6: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + dependencies: + ajv: 6.12.6 + dev: true + /ajv/6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} dependencies: @@ -1761,6 +2944,11 @@ packages: uri-js: 4.4.1 dev: true + /ansi-colors/4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + dev: true + /ansi-escapes/4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} @@ -1771,6 +2959,10 @@ packages: /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + + /ansi-regex/6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} dev: true /ansi-styles/3.2.1: @@ -1784,13 +2976,17 @@ packages: engines: {node: '>=8'} dependencies: color-convert: 2.0.1 - dev: true /ansi-styles/5.2.0: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} dev: true + /ansi-styles/6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + /anymatch/3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1809,6 +3005,21 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-hidden/1.2.2_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-6y/ogyDTk/7YAe91T3E2PR1ALVKyM2QbTio5HwM+N1Q6CMlCKhvClyIjkckBswa0f2xJhjsfzIGa1yVSe1UMVA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.9.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + react: 18.2.0 + tslib: 2.4.0 + dev: false + /aria-query/4.2.2: resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==} engines: {node: '>=6.0'} @@ -1853,20 +3064,37 @@ packages: es-shim-unscopables: 1.0.0 dev: true + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + /ast-types-flow/0.0.7: resolution: {integrity: sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==} dev: true - /async-limiter/1.0.1: - resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + /astral-regex/2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + + /autosize/5.0.2: + resolution: {integrity: sha512-FPVt5ynkqUAA9gcMZnJHka1XfQgr1WNd/yRfIjmj5WGmjua+u5Hl9hn8M2nU5CNy2bEIcj1ZUwXq7IOHsfZG9w==} dev: false - optional: true /axe-core/4.4.3: resolution: {integrity: sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==} engines: {node: '>=4'} dev: true + /axios/0.21.4: + resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + dependencies: + follow-redirects: 1.15.2 + transitivePeerDependencies: + - debug + dev: true + /axobject-query/2.2.0: resolution: {integrity: sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==} dev: true @@ -1954,18 +3182,27 @@ packages: /balanced-match/1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /base64-js/1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false + /better-path-resolve/1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + dependencies: + is-windows: 1.0.2 + dev: true + + /big.js/5.2.2: + resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} + dev: true + /brace-expansion/1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - dev: true /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -1974,6 +3211,12 @@ packages: fill-range: 7.0.1 dev: true + /breakword/1.0.5: + resolution: {integrity: sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==} + dependencies: + wcwidth: 1.0.1 + dev: true + /browserslist/4.21.4: resolution: {integrity: sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -2006,14 +3249,6 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true - /buffer/5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: false - optional: true - /buffer/6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} dependencies: @@ -2026,11 +3261,10 @@ packages: engines: {node: '>=14.16'} dev: true - /cacheable-request/10.2.3: - resolution: {integrity: sha512-6BehRBOs7iurNjAYN9iPazTwFDaMQavJO8W1MEm3s2pH8q/tkPTtLDRUZaweWK87WFGf2Y5wLAlaCJlR5kOz3w==} + /cacheable-request/10.2.4: + resolution: {integrity: sha512-IWIea8ei1Ht4dBqvlvh7Gs7EYlMyBhlJybLDUB9sadEqHqftmdNieMLIR5ia3vs8gbjj9t8hXLBpUVg3vcQNbg==} engines: {node: '>=14.16'} dependencies: - '@types/http-cache-semantics': 4.0.1 get-stream: 6.0.1 http-cache-semantics: 4.1.0 keyv: 4.5.2 @@ -2049,6 +3283,15 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -2062,6 +3305,18 @@ packages: /caniuse-lite/1.0.30001419: resolution: {integrity: sha512-aFO1r+g6R7TW+PNQxKzjITwLOyDhVRLjW0LcwS/HCZGUUKTGNp9+IwLC4xyDSZBygVL/mxaFR3HIV6wEKQuSzw==} + /chalk-next/6.1.5: + resolution: {integrity: sha512-OAx9F3vSk18qpfCohk0849/j3GyaoIpv8eXjmpdbmLZt+5+sWYq8xwt3B5ue25irLcxFcLL2hAbxxHSsBxupbw==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + axios: 0.21.4 + fs: 0.0.1-security + supports-color: 7.2.0 + transitivePeerDependencies: + - debug + dev: true + /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2083,6 +3338,10 @@ packages: engines: {node: '>=10'} dev: true + /chardet/0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + dev: true + /ci-info/3.7.0: resolution: {integrity: sha512-2CpRNYmImPx+RXKLq6jko/L07phmS9I02TyqkcNU20GCF/GgaWvc58hPtjxDX8lPpkdwc9sNh72V9k00S7ezog==} engines: {node: '>=8'} @@ -2092,10 +3351,54 @@ packages: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} dev: true + /clean-stack/2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + dev: true + + /cli-cursor/3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + dependencies: + restore-cursor: 3.1.0 + dev: true + + /cli-truncate/2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + dev: true + + /cli-truncate/3.1.0: + resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + slice-ansi: 5.0.0 + string-width: 5.1.2 + dev: true + /client-only/0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} dev: false + /cliui/6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + dev: true + + /cliui/7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + /cliui/8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -2105,6 +3408,11 @@ packages: wrap-ansi: 7.0.0 dev: true + /clone/1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + dev: true + /clone/2.1.2: resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} engines: {node: '>=0.8'} @@ -2115,6 +3423,20 @@ packages: engines: {node: '>=6'} dev: false + /cmdk/0.1.20_7ey2zzynotv32rpkwno45fsx4e: + resolution: {integrity: sha512-b05kPE+9jmGRibOf2h34d1ybCFfYYOiwsyylDtvhI0ptDSJ/gbPDQSq6DySL4b74ZnK/JH/WVP3UPuAYXRPypg==} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@radix-ui/react-dialog': 1.0.0_7ey2zzynotv32rpkwno45fsx4e + command-score: 0.1.2 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + transitivePeerDependencies: + - '@types/react' + dev: false + /co/4.6.0: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} @@ -2134,18 +3456,28 @@ packages: engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - dev: true /color-name/1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /colorette/2.0.19: + resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} + dev: true + + /command-score/0.1.2: + resolution: {integrity: sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==} + dev: false + + /commander/9.4.1: + resolution: {integrity: sha512-5EEkTNyHNGFPD2H+c/dXXfQZYa/scCKasxWcXJaWnNJ99pnQN9Vnmqow+p+PlFPE63Q6mThaZws1T+HxfpgtPw==} + engines: {node: ^12.20.0 || >=14} dev: true /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} - dev: true /convert-source-map/1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} @@ -2174,6 +3506,14 @@ packages: yaml: 1.10.2 dev: false + /cross-spawn/5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + dev: true + /cross-spawn/7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2192,10 +3532,36 @@ packages: /csstype/3.1.1: resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + /csv-generate/3.4.3: + resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} + dev: true + + /csv-parse/4.16.3: + resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} + dev: true + + /csv-stringify/5.6.5: + resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} + dev: true + + /csv/5.5.3: + resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} + engines: {node: '>= 0.1.90'} + dependencies: + csv-generate: 3.4.3 + csv-parse: 4.16.3 + csv-stringify: 5.6.5 + stream-transform: 2.1.3 + dev: true + /damerau-levenshtein/1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} dev: true + /dayjs/1.11.7: + resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==} + dev: false + /debug/2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -2229,6 +3595,19 @@ packages: dependencies: ms: 2.1.2 + /decamelize-keys/1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize/1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + /decompress-response/6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -2260,20 +3639,17 @@ packages: engines: {node: '>=0.10.0'} dev: true + /defaults/1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + dependencies: + clone: 1.0.4 + dev: true + /defer-to-connect/2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} dev: true - /deferred-leveldown/5.3.0: - resolution: {integrity: sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==} - engines: {node: '>=6'} - dependencies: - abstract-leveldown: 6.2.3 - inherits: 2.0.4 - dev: false - optional: true - /define-properties/1.1.4: resolution: {integrity: sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==} engines: {node: '>= 0.4'} @@ -2281,11 +3657,20 @@ packages: has-property-descriptors: 1.0.0 object-keys: 1.1.1 + /detect-indent/6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + dev: true + /detect-newline/3.1.0: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} dev: true + /detect-node-es/1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /diff-sequences/29.3.1: resolution: {integrity: sha512-hlM3QR272NXCi4pq+N4Kok4kOp6EsgOM3ZSpJI7Da3UAs+Ttsi8MRmB6trM/lhyzUxGfOgnpkHtgqm5Q/CTcfQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2329,6 +3714,10 @@ packages: readable-stream: 2.3.7 dev: false + /eastasianwidth/0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /electron-to-chromium/1.4.284: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true @@ -2340,35 +3729,33 @@ packages: /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /emoji-regex/9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} dev: true - /encoding-down/6.3.0: - resolution: {integrity: sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==} - engines: {node: '>=6'} + /emojis-list/3.0.0: + resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} + engines: {node: '>= 4'} + dev: true + + /encoding/0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} dependencies: - abstract-leveldown: 6.3.0 - inherits: 2.0.4 - level-codec: 9.0.2 - level-errors: 2.0.1 + iconv-lite: 0.6.3 dev: false - optional: true + + /enquirer/2.3.6: + resolution: {integrity: sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==} + engines: {node: '>=8.6'} + dependencies: + ansi-colors: 4.1.3 + dev: true /err-code/3.0.1: resolution: {integrity: sha512-GiaH0KJUewYok+eeY05IIgjtAe4Yltygk9Wqp1V5yVWLdhf0hYZchRjNIT9bb0mSwRcIusT3cx7PJUf3zEIfUA==} dev: false - /errno/0.1.8: - resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} - hasBin: true - dependencies: - prr: 1.0.1 - dev: false - optional: true - /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: @@ -2421,7 +3808,6 @@ packages: /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - dev: true /escape-string-regexp/1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} @@ -2447,11 +3833,11 @@ packages: dependencies: '@next/eslint-plugin-next': 12.3.1 '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/parser': 5.38.0_76twfck5d7crjqrmw4yltga7zm + '@typescript-eslint/parser': 5.47.0_76twfck5d7crjqrmw4yltga7zm eslint: 8.22.0 eslint-import-resolver-node: 0.3.6 eslint-import-resolver-typescript: 2.7.1_2iahngt3u2tkbdlu6s4gkur3pu - eslint-plugin-import: 2.26.0_dz6mtv6jua3j7xbldvgsafodwi + eslint-plugin-import: 2.26.0_erzq45onfaeij7wmfvjxpvebie eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0 eslint-plugin-react: 7.31.8_eslint@8.22.0 eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0 @@ -2461,7 +3847,7 @@ packages: - supports-color dev: true - /eslint-config-next/12.3.1_tg6quxtr5dyl3tikvj7rwonxxi: + /eslint-config-next/12.3.1_f5jcydormhcqaoeadqwgqigppy: resolution: {integrity: sha512-EN/xwKPU6jz1G0Qi6Bd/BqMnHLyRAL0VsaQaWA7F3KkjAgZHi4f1uL1JKGWNxdQpHTW/sdGONBd0bzxUka/DJg==} peerDependencies: eslint: ^7.23.0 || ^8.0.0 @@ -2472,14 +3858,14 @@ packages: dependencies: '@next/eslint-plugin-next': 12.3.1 '@rushstack/eslint-patch': 1.2.0 - '@typescript-eslint/parser': 5.38.0_tg6quxtr5dyl3tikvj7rwonxxi - eslint: 8.22.0 + '@typescript-eslint/parser': 5.47.0_f5jcydormhcqaoeadqwgqigppy + eslint: 8.30.0 eslint-import-resolver-node: 0.3.6 - eslint-import-resolver-typescript: 2.7.1_2iahngt3u2tkbdlu6s4gkur3pu - eslint-plugin-import: 2.26.0_dz6mtv6jua3j7xbldvgsafodwi - eslint-plugin-jsx-a11y: 6.6.1_eslint@8.22.0 - eslint-plugin-react: 7.31.8_eslint@8.22.0 - eslint-plugin-react-hooks: 4.6.0_eslint@8.22.0 + eslint-import-resolver-typescript: 2.7.1_2lbwmhbr7bncddqbzzpg77o75m + eslint-plugin-import: 2.26.0_r5yriwyq5glbgsbtddyqgajogq + eslint-plugin-jsx-a11y: 6.6.1_eslint@8.30.0 + eslint-plugin-react: 7.31.8_eslint@8.30.0 + eslint-plugin-react-hooks: 4.6.0_eslint@8.30.0 typescript: 4.9.3 transitivePeerDependencies: - eslint-import-resolver-webpack @@ -2495,6 +3881,15 @@ packages: eslint: 8.22.0 dev: true + /eslint-config-prettier/8.5.0_eslint@8.30.0: + resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + dependencies: + eslint: 8.30.0 + dev: true + /eslint-import-resolver-node/0.3.6: resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} dependencies: @@ -2513,7 +3908,7 @@ packages: dependencies: debug: 4.3.4 eslint: 8.22.0 - eslint-plugin-import: 2.26.0_dz6mtv6jua3j7xbldvgsafodwi + eslint-plugin-import: 2.26.0_erzq45onfaeij7wmfvjxpvebie glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.1 @@ -2522,7 +3917,25 @@ packages: - supports-color dev: true - /eslint-module-utils/2.7.4_c3nlkncy4cvyvjj2ycqweyustu: + /eslint-import-resolver-typescript/2.7.1_2lbwmhbr7bncddqbzzpg77o75m: + resolution: {integrity: sha512-00UbgGwV8bSgUv34igBDbTOtKhqoRMy9bFjNehT40bXg6585PNIct8HhXZ0SybqB9rWtXj9crcku8ndDn/gIqQ==} + engines: {node: '>=4'} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + dependencies: + debug: 4.3.4 + eslint: 8.30.0 + eslint-plugin-import: 2.26.0_r5yriwyq5glbgsbtddyqgajogq + glob: 7.2.3 + is-glob: 4.0.3 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-module-utils/2.7.4_5x2zxwgwkh3hozpqf3invsv6ui: resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} engines: {node: '>=4'} peerDependencies: @@ -2543,7 +3956,7 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.38.0_tg6quxtr5dyl3tikvj7rwonxxi + '@typescript-eslint/parser': 5.47.0_76twfck5d7crjqrmw4yltga7zm debug: 3.2.7 eslint: 8.22.0 eslint-import-resolver-node: 0.3.6 @@ -2552,7 +3965,37 @@ packages: - supports-color dev: true - /eslint-plugin-import/2.26.0_dz6mtv6jua3j7xbldvgsafodwi: + /eslint-module-utils/2.7.4_nif3prfnpejayun4zfrdsrpede: + resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + dependencies: + '@typescript-eslint/parser': 5.47.0_f5jcydormhcqaoeadqwgqigppy + debug: 3.2.7 + eslint: 8.30.0 + eslint-import-resolver-node: 0.3.6 + eslint-import-resolver-typescript: 2.7.1_2lbwmhbr7bncddqbzzpg77o75m + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-import/2.26.0_erzq45onfaeij7wmfvjxpvebie: resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} engines: {node: '>=4'} peerDependencies: @@ -2562,14 +4005,45 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.38.0_tg6quxtr5dyl3tikvj7rwonxxi + '@typescript-eslint/parser': 5.47.0_76twfck5d7crjqrmw4yltga7zm array-includes: 3.1.5 array.prototype.flat: 1.3.0 debug: 2.6.9 doctrine: 2.1.0 eslint: 8.22.0 eslint-import-resolver-node: 0.3.6 - eslint-module-utils: 2.7.4_c3nlkncy4cvyvjj2ycqweyustu + eslint-module-utils: 2.7.4_5x2zxwgwkh3hozpqf3invsv6ui + has: 1.0.3 + is-core-module: 2.10.0 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.values: 1.1.5 + resolve: 1.22.1 + tsconfig-paths: 3.14.1 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + dev: true + + /eslint-plugin-import/2.26.0_r5yriwyq5glbgsbtddyqgajogq: + resolution: {integrity: sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + dependencies: + '@typescript-eslint/parser': 5.47.0_f5jcydormhcqaoeadqwgqigppy + array-includes: 3.1.5 + array.prototype.flat: 1.3.0 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 8.30.0 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.4_nif3prfnpejayun4zfrdsrpede has: 1.0.3 is-core-module: 2.10.0 is-glob: 4.0.3 @@ -2605,6 +4079,45 @@ packages: semver: 6.3.0 dev: true + /eslint-plugin-jsx-a11y/6.6.1_eslint@8.30.0: + resolution: {integrity: sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + '@babel/runtime': 7.19.0 + aria-query: 4.2.2 + array-includes: 3.1.5 + ast-types-flow: 0.0.7 + axe-core: 4.4.3 + axobject-query: 2.2.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 8.30.0 + has: 1.0.3 + jsx-ast-utils: 3.3.3 + language-tags: 1.0.5 + minimatch: 3.1.2 + semver: 6.3.0 + dev: true + + /eslint-plugin-prettier/4.2.1_bat6r7ilbbslsp5dhd45s4onze: + resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + eslint: '>=7.28.0' + eslint-config-prettier: '*' + prettier: '>=2.0.0' + peerDependenciesMeta: + eslint-config-prettier: + optional: true + dependencies: + eslint: 8.30.0 + eslint-config-prettier: 8.5.0_eslint@8.30.0 + prettier: 2.7.1 + prettier-linter-helpers: 1.0.0 + dev: true + /eslint-plugin-prettier/4.2.1_i2cojdczqdiurzgttlwdgf764e: resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} @@ -2631,6 +4144,15 @@ packages: eslint: 8.22.0 dev: true + /eslint-plugin-react-hooks/4.6.0_eslint@8.30.0: + resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 8.30.0 + dev: true + /eslint-plugin-react/7.31.8_eslint@8.22.0: resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==} engines: {node: '>=4'} @@ -2654,6 +4176,37 @@ packages: string.prototype.matchall: 4.0.7 dev: true + /eslint-plugin-react/7.31.8_eslint@8.30.0: + resolution: {integrity: sha512-5lBTZmgQmARLLSYiwI71tiGVTLUuqXantZM6vlSY39OaDSV0M7+32K5DnLkmFrwTe+Ksz0ffuLUC91RUviVZfw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + dependencies: + array-includes: 3.1.5 + array.prototype.flatmap: 1.3.0 + doctrine: 2.1.0 + eslint: 8.30.0 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.3 + minimatch: 3.1.2 + object.entries: 1.1.5 + object.fromentries: 2.0.5 + object.hasown: 1.1.1 + object.values: 1.1.5 + prop-types: 15.8.1 + resolve: 2.0.0-next.4 + semver: 6.3.0 + string.prototype.matchall: 4.0.7 + dev: true + + /eslint-scope/5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + dev: true + /eslint-scope/7.1.1: resolution: {integrity: sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2672,6 +4225,16 @@ packages: eslint-visitor-keys: 2.1.0 dev: true + /eslint-utils/3.0.0_eslint@8.30.0: + resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} + engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} + peerDependencies: + eslint: '>=5' + dependencies: + eslint: 8.30.0 + eslint-visitor-keys: 2.1.0 + dev: true + /eslint-visitor-keys/2.1.0: resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} engines: {node: '>=10'} @@ -2729,6 +4292,54 @@ packages: - supports-color dev: true + /eslint/8.30.0: + resolution: {integrity: sha512-MGADB39QqYuzEGov+F/qb18r4i7DohCDOfatHaxI2iGlPuC65bwG2gxgO+7DkyL38dRFaRH7RaRAgU6JKL9rMQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + hasBin: true + dependencies: + '@eslint/eslintrc': 1.4.0 + '@humanwhocodes/config-array': 0.11.8 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.3 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.1.1 + eslint-utils: 3.0.0_eslint@8.30.0 + eslint-visitor-keys: 3.3.0 + espree: 9.4.0 + esquery: 1.4.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.19.0 + grapheme-splitter: 1.0.4 + ignore: 5.2.0 + import-fresh: 3.3.0 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-sdsl: 4.2.0 + js-yaml: 4.1.0 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.1 + regexpp: 3.2.0 + strip-ansi: 6.0.1 + strip-json-comments: 3.1.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + dev: true + /espree/9.4.0: resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2758,6 +4369,11 @@ packages: estraverse: 5.3.0 dev: true + /estraverse/4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + dev: true + /estraverse/5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -2787,6 +4403,21 @@ packages: strip-final-newline: 2.0.0 dev: true + /execa/6.1.0: + resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + cross-spawn: 7.0.3 + get-stream: 6.0.1 + human-signals: 3.0.1 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.1.0 + onetime: 6.0.0 + signal-exit: 3.0.7 + strip-final-newline: 3.0.0 + dev: true + /exit/0.1.2: resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} engines: {node: '>= 0.8.0'} @@ -2807,6 +4438,19 @@ packages: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: false + /extendable-error/0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + dev: true + + /external-editor/3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + dev: true + /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -2844,6 +4488,13 @@ packages: reusify: 1.0.4 dev: true + /faye-websocket/0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + dependencies: + websocket-driver: 0.7.4 + dev: false + /fb-watchman/2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} dependencies: @@ -2884,6 +4535,48 @@ packages: path-exists: 4.0.0 dev: true + /find-yarn-workspace-root2/1.2.16: + resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} + dependencies: + micromatch: 4.0.5 + pkg-dir: 4.2.0 + dev: true + + /firebase/9.13.0_encoding@0.1.13: + resolution: {integrity: sha512-xaw5DVbjuT9fbTx+ko/l828LA75zC7H2CJIdRSqMiYmYJjEuNIEb6HHFKUElKn7WmYIF62F2zXe1O8rfP9whgw==} + dependencies: + '@firebase/analytics': 0.8.4_@firebase+app@0.8.3 + '@firebase/analytics-compat': 0.1.17_uhwflrzfcfz2g667jhvqg7sd7i + '@firebase/app': 0.8.3 + '@firebase/app-check': 0.5.16_@firebase+app@0.8.3 + '@firebase/app-check-compat': 0.2.16_uhwflrzfcfz2g667jhvqg7sd7i + '@firebase/app-compat': 0.1.38 + '@firebase/app-types': 0.8.1 + '@firebase/auth': 0.20.11_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/auth-compat': 0.2.24_6e25ipnw6ok3e747dgryxrjf7m + '@firebase/database': 0.13.10_@firebase+app-types@0.8.1 + '@firebase/database-compat': 0.2.10_@firebase+app-types@0.8.1 + '@firebase/firestore': 3.7.2_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/firestore-compat': 0.2.2_6e25ipnw6ok3e747dgryxrjf7m + '@firebase/functions': 0.8.8_qxaep7pmgqjgshotoaytuprcse + '@firebase/functions-compat': 0.2.8_6e25ipnw6ok3e747dgryxrjf7m + '@firebase/installations': 0.5.16_@firebase+app@0.8.3 + '@firebase/installations-compat': 0.1.16_h7lquf2icoxthdkgvez4xhlpaa + '@firebase/messaging': 0.10.0_@firebase+app@0.8.3 + '@firebase/messaging-compat': 0.1.20_uhwflrzfcfz2g667jhvqg7sd7i + '@firebase/performance': 0.5.16_@firebase+app@0.8.3 + '@firebase/performance-compat': 0.1.16_uhwflrzfcfz2g667jhvqg7sd7i + '@firebase/remote-config': 0.3.15_@firebase+app@0.8.3 + '@firebase/remote-config-compat': 0.1.16_uhwflrzfcfz2g667jhvqg7sd7i + '@firebase/storage': 0.9.13_fg26na3iiuycy3cwzcyxqwfb2e + '@firebase/storage-compat': 0.1.21_6e25ipnw6ok3e747dgryxrjf7m + '@firebase/util': 1.7.3 + transitivePeerDependencies: + - bufferutil + - encoding + - utf-8-validate + dev: false + /flat-cache/3.0.4: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -2900,13 +4593,44 @@ packages: resolution: {integrity: sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg==} dev: false + /follow-redirects/1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: true + /form-data-encoder/2.1.4: resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} engines: {node: '>= 14.17'} dev: true + /fs-extra/7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + + /fs-extra/8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.10 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: true + /fs.realpath/1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + /fs/0.0.1-security: + resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==} dev: true /fsevents/2.3.2: @@ -2949,7 +4673,6 @@ packages: /get-caller-file/2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-intrinsic/1.1.3: resolution: {integrity: sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==} @@ -2958,6 +4681,11 @@ packages: has: 1.0.3 has-symbols: 1.0.3 + /get-nonce/1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-package-type/0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -3010,7 +4738,6 @@ packages: minimatch: 3.1.2 once: 1.4.0 path-is-absolute: 1.0.1 - dev: true /globals/11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -3024,6 +4751,13 @@ packages: type-fest: 0.20.2 dev: true + /globals/13.19.0: + resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} + engines: {node: '>=8'} + dependencies: + type-fest: 0.20.2 + dev: true + /globby/11.1.0: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} @@ -3043,7 +4777,7 @@ packages: '@sindresorhus/is': 5.3.0 '@szmarczak/http-timer': 5.0.1 cacheable-lookup: 7.0.0 - cacheable-request: 10.2.3 + cacheable-request: 10.2.4 decompress-response: 6.0.0 form-data-encoder: 2.1.4 get-stream: 6.0.1 @@ -3061,6 +4795,11 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + /has-bigints/1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -3095,20 +4834,35 @@ packages: dependencies: function-bind: 1.1.1 + /highlight.js/11.7.0: + resolution: {integrity: sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==} + engines: {node: '>=12.0.0'} + dev: false + /hoist-non-react-statics/3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} dependencies: react-is: 16.13.1 dev: false - /hotkeys-js/3.10.0: - resolution: {integrity: sha512-20xeVdOqcgTkMox0+BqFwADZP7+5dy/9CFPpAinSMh2d0s3b0Hs2V2D+lMh4Hphkf7VE9pwnOl58eP1te+REcg==} + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /hotkeys-js/3.10.1: + resolution: {integrity: sha512-mshqjgTqx8ee0qryHvRgZaZDxTwxam/2yTQmQlqAWS3+twnq1jsY9Yng9zB7lWq6WRrjTbTOc7knNwccXQiAjQ==} dev: false /html-escaper/2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} dev: true + /html-parse-stringify/3.0.1: + resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + dependencies: + void-elements: 3.1.0 + dev: false + /html-tokenize/2.0.1: resolution: {integrity: sha512-QY6S+hZ0f5m1WT8WffYN+Hg+xm/w5I8XeUcAq/ZYP5wVC8xbKi4Whhru3FtrAebD5EhBW8rmFzkDI6eCAuFe2w==} dependencies: @@ -3123,6 +4877,10 @@ packages: resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} dev: true + /http-parser-js/0.5.8: + resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} + dev: false + /http2-wrapper/2.2.0: resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==} engines: {node: '>=10.19.0'} @@ -3131,17 +4889,56 @@ packages: resolve-alpn: 1.2.1 dev: true + /human-id/1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + dev: true + /human-signals/2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} dev: true + /human-signals/3.0.1: + resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} + engines: {node: '>=12.20.0'} + dev: true + + /husky/8.0.2: + resolution: {integrity: sha512-Tkv80jtvbnkK3mYWxPZePGFpQ/tT3HNSs/sasF9P2YfkMezDl3ON37YN6jUUI4eTg5LcyVynlb6r4eyvOmspvg==} + engines: {node: '>=14'} + hasBin: true + dev: true + + /i18next/21.10.0: + resolution: {integrity: sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg==} + dependencies: + '@babel/runtime': 7.19.0 + dev: false + + /iconv-lite/0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: true + + /iconv-lite/0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /idb-keyval/6.2.0: resolution: {integrity: sha512-uw+MIyQn2jl3+hroD7hF8J7PUviBU7BPKWw4f/ISf32D4LoGu98yHjrzWWJDASu9QNrX10tCJqk9YY0ClWm8Ng==} dependencies: safari-14-idb-fix: 3.0.0 dev: false + /idb/7.0.1: + resolution: {integrity: sha512-UUxlE7vGWK5RfB/fDwEGgRf84DY/ieqNha6msMV99UsEMQhJ1RwbCd8AYBj3QMgnE3VZnfQvm4oKVCJTYlqIgg==} + dev: false + /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: false @@ -3151,10 +4948,9 @@ packages: engines: {node: '>= 4'} dev: true - /immediate/3.3.0: - resolution: {integrity: sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==} + /immediate/3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} dev: false - optional: true /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} @@ -3177,12 +4973,16 @@ packages: engines: {node: '>=0.8.19'} dev: true + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits/2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3196,6 +4996,12 @@ packages: side-channel: 1.0.4 dev: true + /invariant/2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /is-arguments/1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -3226,6 +5032,13 @@ packages: engines: {node: '>= 0.4'} dev: true + /is-ci/3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + dependencies: + ci-info: 3.7.0 + dev: true + /is-core-module/2.10.0: resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} dependencies: @@ -3245,6 +5058,10 @@ packages: /is-fullwidth-code-point/3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + + /is-fullwidth-code-point/4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} dev: true /is-generator-fn/2.1.0: @@ -3276,6 +5093,16 @@ packages: engines: {node: '>=0.12.0'} dev: true + /is-path-inside/3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + dev: true + + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + /is-regex/1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -3294,6 +5121,11 @@ packages: engines: {node: '>=8'} dev: true + /is-stream/3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dev: true + /is-string/1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -3301,6 +5133,13 @@ packages: has-tostringtag: 1.0.0 dev: true + /is-subdir/1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + dependencies: + better-path-resolve: 1.0.0 + dev: true + /is-symbol/1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -3314,6 +5153,11 @@ packages: call-bind: 1.0.2 dev: true + /is-windows/1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + dev: true + /isarray/0.0.1: resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==} dev: false @@ -3392,7 +5236,7 @@ packages: '@jest/expect': 29.3.1 '@jest/test-result': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 co: 4.6.0 dedent: 0.7.0 @@ -3411,7 +5255,7 @@ packages: - supports-color dev: true - /jest-cli/29.3.1_@types+node@18.7.18: + /jest-cli/29.3.1_@types+node@18.11.17: resolution: {integrity: sha512-TO/ewvwyvPOiBBuWZ0gm04z3WWP8TIK8acgPzE4IxgsLKQgb377NYGrQLc3Wl/7ndWzIH2CDNNsUjGxwLL43VQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -3428,7 +5272,7 @@ packages: exit: 0.1.2 graceful-fs: 4.2.10 import-local: 3.1.0 - jest-config: 29.3.1_@types+node@18.7.18 + jest-config: 29.3.1_@types+node@18.11.17 jest-util: 29.3.1 jest-validate: 29.3.1 prompts: 2.4.2 @@ -3439,7 +5283,7 @@ packages: - ts-node dev: true - /jest-config/29.3.1_@types+node@18.7.18: + /jest-config/29.3.1_@types+node@18.11.17: resolution: {integrity: sha512-y0tFHdj2WnTEhxmGUK1T7fgLen7YK4RtfvpLFBXfQkh2eMJAQq24Vx9472lvn5wg0MAO6B+iPfJfzdR9hJYalg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -3454,7 +5298,7 @@ packages: '@babel/core': 7.20.5 '@jest/test-sequencer': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 babel-jest: 29.3.1_@babel+core@7.20.5 chalk: 4.1.2 ci-info: 3.7.0 @@ -3513,7 +5357,7 @@ packages: '@jest/environment': 29.3.1 '@jest/fake-timers': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 jest-mock: 29.3.1 jest-util: 29.3.1 dev: true @@ -3529,7 +5373,7 @@ packages: dependencies: '@jest/types': 29.3.1 '@types/graceful-fs': 4.1.5 - '@types/node': 18.7.18 + '@types/node': 18.11.17 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.10 @@ -3580,7 +5424,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 jest-util: 29.3.1 dev: true @@ -3635,7 +5479,7 @@ packages: '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.10 @@ -3666,7 +5510,7 @@ packages: '@jest/test-result': 29.3.1 '@jest/transform': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -3722,7 +5566,7 @@ packages: engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 chalk: 4.1.2 ci-info: 3.7.0 graceful-fs: 4.2.10 @@ -3747,7 +5591,7 @@ packages: dependencies: '@jest/test-result': 29.3.1 '@jest/types': 29.3.1 - '@types/node': 18.7.18 + '@types/node': 18.11.17 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3759,13 +5603,13 @@ packages: resolution: {integrity: sha512-lY4AnnmsEWeiXirAIA0c9SDPbuCBq8IYuDVL8PMm0MZ2PEs2yPvRA/J64QBXuZp7CYKrDM/rmNrc9/i3KJQncw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - '@types/node': 18.7.18 + '@types/node': 18.11.17 jest-util: 29.3.1 merge-stream: 2.0.0 supports-color: 8.1.1 dev: true - /jest/29.3.1_@types+node@18.7.18: + /jest/29.3.1_@types+node@18.11.17: resolution: {integrity: sha512-6iWfL5DTT0Np6UYs/y5Niu7WIfNv/wRTtN5RSXt2DIEft3dx3zPuw/3WJQBCJfmEzvDiEKwoqMbGD9n49+qLSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} hasBin: true @@ -3778,13 +5622,17 @@ packages: '@jest/core': 29.3.1 '@jest/types': 29.3.1 import-local: 3.1.0 - jest-cli: 29.3.1_@types+node@18.7.18 + jest-cli: 29.3.1_@types+node@18.11.17 transitivePeerDependencies: - '@types/node' - supports-color - ts-node dev: true + /js-sdsl/4.2.0: + resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==} + dev: true + /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3836,6 +5684,12 @@ packages: hasBin: true dev: true + /jsonfile/4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.10 + dev: true + /jsx-ast-utils/3.3.3: resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} engines: {node: '>=4.0'} @@ -3844,17 +5698,41 @@ packages: object.assign: 4.1.4 dev: true + /jszip/3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.7 + setimmediate: 1.0.5 + dev: false + /keyv/4.5.2: resolution: {integrity: sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g==} dependencies: json-buffer: 3.0.1 dev: true + /kind-of/6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + dev: true + /kleur/3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} dev: true + /kleur/4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + dev: true + + /ky/0.33.0: + resolution: {integrity: sha512-peKzuOlN/q3Q3jOgi4t0cp6DOgif5rVnmiSIsjsmkiOcdnSjkrKSUqQmRWYCTqjUtR9b3xQQr8aj7KwSW1r49A==} + engines: {node: '>=14.16'} + dev: false + /language-subtag-registry/0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -3865,98 +5743,6 @@ packages: language-subtag-registry: 0.3.22 dev: true - /level-codec/9.0.2: - resolution: {integrity: sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==} - engines: {node: '>=6'} - dependencies: - buffer: 5.7.1 - dev: false - optional: true - - /level-concat-iterator/2.0.1: - resolution: {integrity: sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==} - engines: {node: '>=6'} - dev: false - optional: true - - /level-errors/2.0.1: - resolution: {integrity: sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==} - engines: {node: '>=6'} - dependencies: - errno: 0.1.8 - dev: false - optional: true - - /level-iterator-stream/4.0.2: - resolution: {integrity: sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==} - engines: {node: '>=6'} - dependencies: - inherits: 2.0.4 - readable-stream: 3.6.0 - xtend: 4.0.2 - dev: false - optional: true - - /level-js/5.0.2: - resolution: {integrity: sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==} - dependencies: - abstract-leveldown: 6.2.3 - buffer: 5.7.1 - inherits: 2.0.4 - ltgt: 2.2.1 - dev: false - optional: true - - /level-packager/5.1.1: - resolution: {integrity: sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==} - engines: {node: '>=6'} - dependencies: - encoding-down: 6.3.0 - levelup: 4.4.0 - dev: false - optional: true - - /level-supports/1.0.1: - resolution: {integrity: sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==} - engines: {node: '>=6'} - dependencies: - xtend: 4.0.2 - dev: false - optional: true - - /level/6.0.1: - resolution: {integrity: sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==} - engines: {node: '>=8.6.0'} - dependencies: - level-js: 5.0.2 - level-packager: 5.1.1 - leveldown: 5.6.0 - dev: false - optional: true - - /leveldown/5.6.0: - resolution: {integrity: sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==} - engines: {node: '>=8.6.0'} - requiresBuild: true - dependencies: - abstract-leveldown: 6.2.3 - napi-macros: 2.0.0 - node-gyp-build: 4.1.1 - dev: false - optional: true - - /levelup/4.4.0: - resolution: {integrity: sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==} - engines: {node: '>=6'} - dependencies: - deferred-leveldown: 5.3.0 - level-errors: 2.0.1 - level-iterator-stream: 4.0.2 - level-supports: 1.0.1 - xtend: 4.0.2 - dev: false - optional: true - /leven/3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3970,16 +5756,69 @@ packages: type-check: 0.4.0 dev: true - /lib0/0.2.52: - resolution: {integrity: sha512-CjxlM7UgICfN6b2OPALBXchIBiNk6jE+1g7JP8ha+dh1xKRDSYpH0WQl1+rMqCju49xUnwPG34v4CR5/rPOZhg==} + /lib0/0.2.58: + resolution: {integrity: sha512-6ovqPaYfOKU7GkkVxz/wjMR0zsqmNsISLvH+h9Lx5YNtWDZey69aYsTGXaSVpUPpJ+ZFtIvcZHsTGL3MbwOM8A==} engines: {node: '>=14'} dependencies: isomorphic.js: 0.2.5 dev: false + /lie/3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + dependencies: + immediate: 3.0.6 + dev: false + + /lilconfig/2.0.6: + resolution: {integrity: sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==} + engines: {node: '>=10'} + dev: true + /lines-and-columns/1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + /lint-staged/13.1.0: + resolution: {integrity: sha512-pn/sR8IrcF/T0vpWLilih8jmVouMlxqXxKuAojmbiGX5n/gDnz+abdPptlj0vYnbfE0SQNl3CY/HwtM0+yfOVQ==} + engines: {node: ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + cli-truncate: 3.1.0 + colorette: 2.0.19 + commander: 9.4.1 + debug: 4.3.4 + execa: 6.1.0 + lilconfig: 2.0.6 + listr2: 5.0.6 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-inspect: 1.12.2 + pidtree: 0.6.0 + string-argv: 0.3.1 + yaml: 2.2.0 + transitivePeerDependencies: + - enquirer + - supports-color + dev: true + + /listr2/5.0.6: + resolution: {integrity: sha512-u60KxKBy1BR2uLJNTWNptzWQ1ob/gjMzIJPZffAENzpZqbMZ/5PrXXOomDcevIS/+IB7s1mmCEtSlT2qHWMqag==} + engines: {node: ^14.13.1 || >=16.0.0} + peerDependencies: + enquirer: '>= 2.3.0 < 3' + peerDependenciesMeta: + enquirer: + optional: true + dependencies: + cli-truncate: 2.1.0 + colorette: 2.0.19 + log-update: 4.0.0 + p-map: 4.0.0 + rfdc: 1.3.0 + rxjs: 7.8.0 + through: 2.3.8 + wrap-ansi: 7.0.0 + dev: true + /lit-element/3.2.2: resolution: {integrity: sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ==} dependencies: @@ -3993,6 +5832,12 @@ packages: '@types/trusted-types': 2.0.2 dev: false + /lit-html/2.5.0: + resolution: {integrity: sha512-bLHosg1XL3JRUcKdSVI0sLCs0y1wWrj2sqqAN3cZ7bDDPNgmDHH29RV48x6Wz3ZmkxIupaE+z7uXSZ/pXWAO1g==} + dependencies: + '@types/trusted-types': 2.0.2 + dev: false + /lit/2.4.0: resolution: {integrity: sha512-fdgzxEtLrZFQU/BqTtxFQCLwlZd9bdat+ltzSFjvWkZrs7eBmeX0L5MHUMb3kYIkuS8Xlfnii/iI5klirF8/Xg==} dependencies: @@ -4001,6 +5846,33 @@ packages: lit-html: 2.4.0 dev: false + /lit/2.5.0: + resolution: {integrity: sha512-DtnUP6vR3l4Q8nRPPNBD+UxbAhwJPeky+OVbi3pdgMqm0g57xFSl1Sj64D1rIB+nVNdiVVg8YxB0hqKjvdadZA==} + dependencies: + '@lit/reactive-element': 1.5.0 + lit-element: 3.2.2 + lit-html: 2.5.0 + dev: false + + /load-yaml-file/0.2.0: + resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.10 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + + /loader-utils/2.0.4: + resolution: {integrity: sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==} + engines: {node: '>=8.9.0'} + dependencies: + big.js: 5.2.2 + emojis-list: 3.0.0 + json5: 2.2.1 + dev: true + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -4015,8 +5887,8 @@ packages: p-locate: 5.0.0 dev: true - /lodash.debounce/4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + /lodash.camelcase/4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} dev: false /lodash.memoize/4.1.2: @@ -4027,10 +5899,32 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.startcase/4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + dev: true + /lodash/4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} dev: false + /log-update/4.0.0: + resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} + engines: {node: '>=10'} + dependencies: + ansi-escapes: 4.3.2 + cli-cursor: 3.1.0 + slice-ansi: 4.0.0 + wrap-ansi: 6.2.0 + dev: true + + /long/4.0.0: + resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} + dev: false + + /long/5.2.0: + resolution: {integrity: sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w==} + dev: false + /loose-envify/1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} dependencies: @@ -4041,6 +5935,13 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /lru-cache/4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -4048,11 +5949,6 @@ packages: yallist: 4.0.0 dev: true - /ltgt/2.2.1: - resolution: {integrity: sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==} - dev: false - optional: true - /make-dir/3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -4070,12 +5966,39 @@ packages: tmpl: 1.0.5 dev: true - /marked/4.1.1: - resolution: {integrity: sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==} + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + + /marked/4.2.5: + resolution: {integrity: sha512-jPueVhumq7idETHkb203WDD4fMA3yV9emQ5vLwop58lu8bTclMghBWcYAavlDqIEMaisADinV1TooIFCfqOsYQ==} engines: {node: '>= 12'} hasBin: true dev: false + /meow/6.1.1: + resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} + engines: {node: '>=8'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 2.5.0 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.13.1 + yargs-parser: 18.1.3 + dev: true + /merge-stream/2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -4098,6 +6021,11 @@ packages: engines: {node: '>=6'} dev: true + /mimic-fn/4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + dev: true + /mimic-response/3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} @@ -4108,15 +6036,33 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 + + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 dev: true /minimist/1.2.6: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + /mixme/0.5.4: + resolution: {integrity: sha512-3KYa4m4Vlqx98GPdOHghxSdNtTvcP8E0kkaJ5Dlh+h2DRzF7zpuVVcA8B0QpKd11YJeP9QQ7ASkKzOeu195Wzw==} + engines: {node: '>= 8.0.0'} + dev: true + /ms/2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} dev: true @@ -4141,15 +6087,18 @@ packages: hasBin: true dev: false - /napi-macros/2.0.0: - resolution: {integrity: sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==} - dev: false - optional: true + /natural-compare-lite/1.4.0: + resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} + dev: true /natural-compare/1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /next-debug-local/0.1.5: + resolution: {integrity: sha512-KnJPlmPJ1ObNxz3suK89H14RqdiSqfZhyca+PnDA+bqSUjjUDvQJmIOQi6CV6dtJpx9isdJu8Pi4reoxm3wyGQ==} + dev: false + /next/12.3.1_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-l7bvmSeIwX5lp07WtIiP9u2ytZMv7jIeB8iacR28PuUEFG5j0HGAPnMqyG5kbZNBG2H7tRsrQ4HCjuMOPnANZw==} engines: {node: '>=12.22.0'} @@ -4195,8 +6144,8 @@ packages: - babel-plugin-macros dev: false - /next/13.0.1_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-ErCNBPIeZMKFn6hX+ZBSlqZVgJIeitEqhGTuQUNmYXJ07/A71DZ7AJI8eyHYUdBb686LUpV1/oBdTq9RpzRVPg==} + /next/13.1.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-lQMZH1V94L5IL/WaihQkTYabSY73aqgrkGPJB5uz+2O3ES4I3losV/maXLY7l7x5e+oNyE9N81upNQ8uRsR5/A==} engines: {node: '>=14.6.0'} hasBin: true peerDependencies: @@ -4213,38 +6162,44 @@ packages: sass: optional: true dependencies: - '@next/env': 13.0.1 - '@swc/helpers': 0.4.11 + '@next/env': 13.1.0 + '@swc/helpers': 0.4.14 caniuse-lite: 1.0.30001419 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - styled-jsx: 5.1.0_react@18.2.0 - use-sync-external-store: 1.2.0_react@18.2.0 + styled-jsx: 5.1.1_react@18.2.0 optionalDependencies: - '@next/swc-android-arm-eabi': 13.0.1 - '@next/swc-android-arm64': 13.0.1 - '@next/swc-darwin-arm64': 13.0.1 - '@next/swc-darwin-x64': 13.0.1 - '@next/swc-freebsd-x64': 13.0.1 - '@next/swc-linux-arm-gnueabihf': 13.0.1 - '@next/swc-linux-arm64-gnu': 13.0.1 - '@next/swc-linux-arm64-musl': 13.0.1 - '@next/swc-linux-x64-gnu': 13.0.1 - '@next/swc-linux-x64-musl': 13.0.1 - '@next/swc-win32-arm64-msvc': 13.0.1 - '@next/swc-win32-ia32-msvc': 13.0.1 - '@next/swc-win32-x64-msvc': 13.0.1 + '@next/swc-android-arm-eabi': 13.1.0 + '@next/swc-android-arm64': 13.1.0 + '@next/swc-darwin-arm64': 13.1.0 + '@next/swc-darwin-x64': 13.1.0 + '@next/swc-freebsd-x64': 13.1.0 + '@next/swc-linux-arm-gnueabihf': 13.1.0 + '@next/swc-linux-arm64-gnu': 13.1.0 + '@next/swc-linux-arm64-musl': 13.1.0 + '@next/swc-linux-x64-gnu': 13.1.0 + '@next/swc-linux-x64-musl': 13.1.0 + '@next/swc-win32-arm64-msvc': 13.1.0 + '@next/swc-win32-ia32-msvc': 13.1.0 + '@next/swc-win32-x64-msvc': 13.1.0 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros dev: false - /node-gyp-build/4.1.1: - resolution: {integrity: sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==} - hasBin: true + /node-fetch/2.6.7_encoding@0.1.13: + resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + encoding: 0.1.13 + whatwg-url: 5.0.0 dev: false - optional: true /node-int64/0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -4254,6 +6209,15 @@ packages: resolution: {integrity: sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==} dev: true + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + /normalize-path/3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -4271,6 +6235,13 @@ packages: path-key: 3.1.1 dev: true + /npm-run-path/5.1.0: + resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + path-key: 4.0.0 + dev: true + /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -4343,7 +6314,6 @@ packages: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - dev: true /onetime/5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -4352,6 +6322,13 @@ packages: mimic-fn: 2.1.0 dev: true + /onetime/6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + dependencies: + mimic-fn: 4.0.0 + dev: true + /optionator/0.9.1: resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} engines: {node: '>= 0.8.0'} @@ -4364,11 +6341,27 @@ packages: word-wrap: 1.2.3 dev: true + /os-tmpdir/1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + dev: true + + /outdent/0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + dev: true + /p-cancelable/3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} dev: true + /p-filter/2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + dependencies: + p-map: 2.1.0 + dev: true + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4397,11 +6390,27 @@ packages: p-limit: 3.1.0 dev: true + /p-map/2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + dev: true + + /p-map/4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + dependencies: + aggregate-error: 3.1.0 + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} dev: true + /pako/1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + dev: false + /parchment/1.1.4: resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} dev: false @@ -4429,13 +6438,17 @@ packages: /path-is-absolute/1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - dev: true /path-key/3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} dev: true + /path-key/4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + dev: true + /path-parse/1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -4443,6 +6456,10 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + /perfect-freehand/1.2.0: + resolution: {integrity: sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==} + dev: false + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} @@ -4451,6 +6468,17 @@ packages: engines: {node: '>=8.6'} dev: true + /pidtree/0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + dev: true + + /pify/4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + dev: true + /pirates/4.0.5: resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} engines: {node: '>= 6'} @@ -4463,8 +6491,8 @@ packages: find-up: 4.1.0 dev: true - /playwright-core/1.28.1: - resolution: {integrity: sha512-3PixLnGPno0E8rSBJjtwqTwJe3Yw72QwBBBxNoukIj3lEeBNXwbNiKrNuB1oyQgTBw5QHUhNO3SteEtHaMK6ag==} + /playwright-core/1.29.1: + resolution: {integrity: sha512-20Ai3d+lMkWpI9YZYlxk8gxatfgax5STW8GaMozAHwigLiyiKQrdkt7gaoT9UQR8FIVDg6qVXs9IoZUQrDjIIg==} engines: {node: '>=14'} hasBin: true dev: true @@ -4478,6 +6506,16 @@ packages: source-map-js: 1.0.2 dev: false + /preferred-pm/3.0.3: + resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} + engines: {node: '>=10'} + dependencies: + find-up: 5.0.0 + find-yarn-workspace-root2: 1.2.16 + path-exists: 4.0.0 + which-pm: 2.0.0 + dev: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -4522,10 +6560,48 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 - /prr/1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} + /protobufjs/6.11.3: + resolution: {integrity: sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==} + hasBin: true + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/long': 4.0.2 + '@types/node': 18.11.17 + long: 4.0.0 dev: false - optional: true + + /protobufjs/7.1.2: + resolution: {integrity: sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 18.11.17 + long: 5.2.0 + dev: false + + /pseudomap/1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + dev: true /punycode/2.1.1: resolution: {integrity: sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==} @@ -4535,6 +6611,11 @@ packages: /queue-microtask/1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + /quick-lru/5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} @@ -4570,6 +6651,16 @@ packages: safe-buffer: 5.2.1 dev: false + /raw-loader/4.0.2: + resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.1.1 + dev: true + /react-dom/18.2.0_react@18.2.0: resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} peerDependencies: @@ -4580,12 +6671,84 @@ packages: scheduler: 0.23.0 dev: false + /react-i18next/11.18.6_vfm63zmruocgezzfl2v26zlzpy: + resolution: {integrity: sha512-yHb2F9BiT0lqoQDt8loZ5gWP331GwctHz9tYQ8A2EIEUu+CcEdjBLQWli1USG3RdWQt3W+jqQLg/d4rrQR96LA==} + peerDependencies: + i18next: '>= 19.0.0' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + dependencies: + '@babel/runtime': 7.19.0 + html-parse-stringify: 3.0.1 + i18next: 21.10.0 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + dev: false + /react-is/16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} /react-is/18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-remove-scroll-bar/2.3.4_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + react: 18.2.0 + react-style-singleton: 2.2.1_w5j4k42lgipnm43s3brx6h3c34 + tslib: 2.4.0 + dev: false + + /react-remove-scroll/2.5.4_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + react: 18.2.0 + react-remove-scroll-bar: 2.3.4_w5j4k42lgipnm43s3brx6h3c34 + react-style-singleton: 2.2.1_w5j4k42lgipnm43s3brx6h3c34 + tslib: 2.4.0 + use-callback-ref: 1.3.0_w5j4k42lgipnm43s3brx6h3c34 + use-sidecar: 1.1.2_w5j4k42lgipnm43s3brx6h3c34 + dev: false + + /react-style-singleton/2.2.1_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.4.0 + dev: false + /react-transition-group/4.4.5_biqbaboplfbrettd7655fr4n2y: resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -4607,6 +6770,35 @@ packages: loose-envify: 1.4.0 dev: false + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + + /read-yaml-file/1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + dependencies: + graceful-fs: 4.2.10 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + dev: true + /readable-stream/1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} dependencies: @@ -4637,6 +6829,18 @@ packages: util-deprecate: 1.0.2 dev: false + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + + /regenerator-runtime/0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: true + /regenerator-runtime/0.13.9: resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==} @@ -4656,6 +6860,9 @@ packages: /require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + + /require-main-filename/2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} dev: true /resolve-alpn/1.2.1: @@ -4705,16 +6912,28 @@ packages: lowercase-keys: 3.0.0 dev: true + /restore-cursor/3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + dev: true + /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} dev: true + /rfdc/1.3.0: + resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} + dev: true + /rimraf/3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + hasBin: true dependencies: glob: 7.2.3 - dev: true /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -4722,6 +6941,12 @@ packages: queue-microtask: 1.2.3 dev: true + /rxjs/7.8.0: + resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + dependencies: + tslib: 2.4.0 + dev: true + /safari-14-idb-fix/3.0.0: resolution: {integrity: sha512-eBNFLob4PMq8JA1dGyFn6G97q3/WzNtFK4RnzT1fnLq+9RyrGknzYiM/9B12MnKAxuj1IXr7UKYtTNtjyKMBog==} dev: false @@ -4734,12 +6959,41 @@ packages: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} dev: false + /safer-buffer/2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + /scheduler/0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: loose-envify: 1.4.0 dev: false + /schema-utils/3.1.1: + resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2_ajv@6.12.6 + dev: true + + /selenium-webdriver/4.5.0: + resolution: {integrity: sha512-9mSFii+lRwcnT2KUAB1kqvx6+mMiiQHH60Y0VUtr3kxxi3oZ3CV3B8e2nuJ7T4SPb+Q6VA0swswe7rYpez07Bg==} + engines: {node: '>= 14.20.0'} + dependencies: + jszip: 3.10.1 + tmp: 0.2.1 + ws: 8.10.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + /semver/6.3.0: resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} hasBin: true @@ -4753,6 +7007,21 @@ packages: lru-cache: 6.0.0 dev: true + /set-blocking/2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + dev: true + + /setimmediate/1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + dev: false + + /shebang-command/1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + dependencies: + shebang-regex: 1.0.0 + dev: true + /shebang-command/2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -4760,6 +7029,11 @@ packages: shebang-regex: 3.0.0 dev: true + /shebang-regex/1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + dev: true + /shebang-regex/3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} @@ -4800,6 +7074,45 @@ packages: engines: {node: '>=8'} dev: true + /slice-ansi/3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + + /slice-ansi/5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + dev: true + + /smartwrap/2.0.2: + resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} + engines: {node: '>=6'} + hasBin: true + dependencies: + array.prototype.flat: 1.3.0 + breakword: 1.0.5 + grapheme-splitter: 1.0.4 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 15.4.1 + dev: true + /source-map-js/1.0.2: resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} engines: {node: '>=0.10.0'} @@ -4822,6 +7135,35 @@ packages: engines: {node: '>=0.10.0'} dev: true + /spawndamnit/2.0.0: + resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} + dependencies: + cross-spawn: 5.1.0 + signal-exit: 3.0.7 + dev: true + + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + /sprintf-js/1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -4833,6 +7175,17 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stream-transform/2.1.3: + resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} + dependencies: + mixme: 0.5.4 + dev: true + + /string-argv/0.3.1: + resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} + engines: {node: '>=0.6.19'} + dev: true + /string-length/4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -4848,6 +7201,14 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + + /string-width/5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.0.1 dev: true /string.prototype.matchall/4.0.7: @@ -4900,6 +7261,12 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 + + /strip-ansi/7.0.1: + resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 dev: true /strip-bom/3.0.0: @@ -4917,6 +7284,18 @@ packages: engines: {node: '>=6'} dev: true + /strip-final-newline/3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + dev: true + + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -4938,8 +7317,8 @@ packages: react: 18.2.0 dev: false - /styled-jsx/5.1.0_react@18.2.0: - resolution: {integrity: sha512-/iHaRJt9U7T+5tp6TRelLnqBqiaIT0HsO0+vgyj8hK2KUk7aejFqRrumqPUlAqDwAj8IbS/1hk3IhBAAK/FCUQ==} + /styled-jsx/5.1.1_react@18.2.0: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' @@ -4983,6 +7362,20 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + /swr/2.0.0: + resolution: {integrity: sha512-IhUx5yPkX+Fut3h0SqZycnaNLXLXsb2ECFq0Y29cxnK7d8r7auY2JWNbCW3IX+EqXUg3rwNJFlhrw5Ye/b6k7w==} + engines: {pnpm: '7'} + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 + dependencies: + use-sync-external-store: 1.2.0 + dev: false + + /term-size/2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + dev: true + /test-exclude/6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -4998,7 +7391,6 @@ packages: /through/2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - dev: false /through2/0.4.2: resolution: {integrity: sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==} @@ -5007,6 +7399,20 @@ packages: xtend: 2.1.2 dev: false + /tmp/0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + dependencies: + os-tmpdir: 1.0.2 + dev: true + + /tmp/0.2.1: + resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} + engines: {node: '>=8.17.0'} + dependencies: + rimraf: 3.0.2 + dev: false + /tmpl/1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} dev: true @@ -5022,6 +7428,15 @@ packages: is-number: 7.0.0 dev: true + /tr46/0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + /ts-jest/29.0.3_4f6uxrzmuwipl5rr3bcogf6k74: resolution: {integrity: sha512-Ibygvmuyq1qp/z3yTh9QTwVVAbFdDy/+4BtIQR2sp6baF2SJU/8CKK/hhnGIDY2L90Az2jIqTwZPnN2p+BweiQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -5045,7 +7460,7 @@ packages: dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.3.1_@types+node@18.7.18 + jest: 29.3.1_@types+node@18.11.17 jest-util: 29.3.1 json5: 2.2.1 lodash.memoize: 4.1.2 @@ -5070,7 +7485,6 @@ packages: /tslib/2.4.0: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} - dev: false /tsutils/3.21.0_typescript@4.8.3: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -5092,6 +7506,20 @@ packages: typescript: 4.9.3 dev: true + /tty-table/4.1.6: + resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + csv: 5.5.3 + kleur: 4.1.5 + smartwrap: 2.0.2 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + yargs: 17.6.2 + dev: true + /turndown/7.1.1: resolution: {integrity: sha512-BEkXaWH7Wh7e9bd2QumhfAXk5g34+6QUmmWx+0q6ThaVOLuLUqsnkq35HQ5SBHSaxjSfSM7US5o4lhJNH7B9MA==} dependencies: @@ -5110,6 +7538,11 @@ packages: engines: {node: '>=4'} dev: true + /type-fest/0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -5120,6 +7553,16 @@ packages: engines: {node: '>=10'} dev: true + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + /typescript/4.8.3: resolution: {integrity: sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==} engines: {node: '>=4.2.0'} @@ -5147,6 +7590,11 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /universalify/0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: true + /update-browserslist-db/1.0.10_browserslist@4.21.4: resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} hasBin: true @@ -5164,6 +7612,43 @@ packages: punycode: 2.1.1 dev: true + /use-callback-ref/1.3.0_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + react: 18.2.0 + tslib: 2.4.0 + dev: false + + /use-sidecar/1.1.2_w5j4k42lgipnm43s3brx6h3c34: + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.20 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.4.0 + dev: false + + /use-sync-external-store/1.2.0: + resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + dev: false + /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -5189,12 +7674,55 @@ packages: convert-source-map: 1.9.0 dev: true + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + + /void-elements/3.1.0: + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} + engines: {node: '>=0.10.0'} + dev: false + /walker/1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: makeerror: 1.0.12 dev: true + /wcwidth/1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + dependencies: + defaults: 1.0.4 + dev: true + + /webidl-conversions/3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /websocket-driver/0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + dependencies: + http-parser-js: 0.5.8 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + dev: false + + /websocket-extensions/0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + dev: false + + /whatwg-url/5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false + /which-boxed-primitive/1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -5205,6 +7733,25 @@ packages: is-symbol: 1.0.4 dev: true + /which-module/2.0.0: + resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} + dev: true + + /which-pm/2.0.0: + resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} + engines: {node: '>=8.15'} + dependencies: + load-yaml-file: 0.2.0 + path-exists: 4.0.0 + dev: true + + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5217,6 +7764,15 @@ packages: engines: {node: '>=0.10.0'} dev: true + /wrap-ansi/6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /wrap-ansi/7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -5224,11 +7780,9 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrappy/1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true /write-file-atomic/4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} @@ -5238,22 +7792,6 @@ packages: signal-exit: 3.0.7 dev: true - /ws/6.2.2: - resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==} - requiresBuild: true - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dependencies: - async-limiter: 1.0.1 - dev: false - optional: true - /ws/7.5.9: resolution: {integrity: sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==} engines: {node: '>=8.3.0'} @@ -5269,6 +7807,19 @@ packages: dev: false optional: true + /ws/8.10.0: + resolution: {integrity: sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: false + /xtend/2.1.2: resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==} engines: {node: '>=0.4'} @@ -5276,37 +7827,10 @@ packages: object-keys: 0.4.0 dev: false - /xtend/4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: false - optional: true - - /y-indexeddb/9.0.9_yjs@13.5.41: - resolution: {integrity: sha512-GcJbiJa2eD5hankj46Hea9z4hbDnDjvh1fT62E5SpZRsv8GcEemw34l1hwI2eknGcv5Ih9JfusT37JLx9q3LFg==} - peerDependencies: - yjs: ^13.0.0 - dependencies: - lib0: 0.2.52 - yjs: 13.5.41 - dev: false - - /y-leveldb/0.1.1_yjs@13.5.41: - resolution: {integrity: sha512-L8Q0MQmxCQ0qWIOuPzLbWn95TNhrCI7M6LaHnilU4I2IX08e4Dmfg5Tgy4JZ3tnl2aiuZyDOJplHl/msIB/IsA==} - requiresBuild: true - peerDependencies: - yjs: ^13.0.0 - dependencies: - level: 6.0.1 - lib0: 0.2.52 - yjs: 13.5.41 - dev: false - optional: true - /y-protocols/1.0.5: resolution: {integrity: sha512-Wil92b7cGk712lRHDqS4T90IczF6RkcvCwAD0A2OPg+adKmOe+nOiT/N2hvpQIWS3zfjmtL4CPaH5sIW1Hkm/A==} dependencies: - lib0: 0.2.52 + lib0: 0.2.58 dev: false /y-webrtc/10.2.3: @@ -5314,7 +7838,7 @@ packages: engines: {node: '>=12'} hasBin: true dependencies: - lib0: 0.2.52 + lib0: 0.2.58 simple-peer: 9.11.1 y-protocols: 1.0.5 optionalDependencies: @@ -5325,27 +7849,16 @@ packages: - utf-8-validate dev: false - /y-websocket/1.4.5_yjs@13.5.41: - resolution: {integrity: sha512-5d9LTSy0GQKqSd/FKRo5DMBlsiTlCipbKcIgPLlno+5xHtfT8bm3uQdcbY9JvLfckojilLZWauXJu0vzDZX05w==} - hasBin: true - peerDependencies: - yjs: ^13.5.6 - dependencies: - lib0: 0.2.52 - lodash.debounce: 4.0.8 - y-protocols: 1.0.5 - yjs: 13.5.41 - optionalDependencies: - ws: 6.2.2 - y-leveldb: 0.1.1_yjs@13.5.41 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - dev: false + /y18n/4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + dev: true /y18n/5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + + /yallist/2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} dev: true /yallist/4.0.0: @@ -5357,11 +7870,59 @@ packages: engines: {node: '>= 6'} dev: false + /yaml/2.2.0: + resolution: {integrity: sha512-auf7Gi6QwO7HW//GA9seGvTXVGWl1CM/ADWh1+RxtXr6XOxnT65ovDl9fTi4e0monEyJxCHqDpF6QnFDXmJE4g==} + engines: {node: '>= 14'} + dev: true + + /yargs-parser/18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + dev: true + + /yargs-parser/20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false + /yargs-parser/21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} dev: true + /yargs/15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.0 + y18n: 4.0.3 + yargs-parser: 18.1.3 + dev: true + + /yargs/16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: false + /yargs/17.6.2: resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} engines: {node: '>=12'} @@ -5375,10 +7936,10 @@ packages: yargs-parser: 21.1.1 dev: true - /yjs/13.5.41: - resolution: {integrity: sha512-4eSTrrs8OeI0heXKKioRY4ag7V5Bk85Z4MeniUyown3o3y0G7G4JpAZWrZWfTp7pzw2b53GkAQWKqHsHi9j9JA==} + /yjs/13.5.43: + resolution: {integrity: sha512-NJqWuiDOseYjkhnSVo55z+FZD6TsOJBZfMbH2I4OCm5vsgY7TESUjUGb7Pt1lljvvdSfBVj8CxQqZAnVxe5Iyg==} dependencies: - lib0: 0.2.52 + lib0: 0.2.58 dev: false /yocto-queue/0.1.0: diff --git a/scripts/notify.mjs b/scripts/notify.mjs index b404731546..117b0d5d4d 100644 --- a/scripts/notify.mjs +++ b/scripts/notify.mjs @@ -2,15 +2,17 @@ import got from 'got'; const STAGE_HOST = 'https://nightly.affine.pro/'; if (['master', 'develop'].includes(process.env.CF_PAGES_BRANCH)) { - const message = `Daily builds: New deployment of PathFinder version ${process.env.CF_PAGES_COMMIT_SHA} was deploy: [nightly](${STAGE_HOST}) / [internal](${process.env.CF_PAGES_URL})`; - const url = `https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`; + const message = `Daily builds: New deployment of PathFinder version ${process.env.CF_PAGES_COMMIT_SHA} was deploy: [nightly](${STAGE_HOST}) / [internal](${process.env.CF_PAGES_URL})`; + const url = `https://api.telegram.org/bot${process.env.BOT_TOKEN}/sendMessage`; - got.post(url, { - json: { - chat_id: process.env.CHAT_ID, - text: message, - parse_mode: 'Markdown', - disable_notification: true, - }, - }).then(r => console.log(r.body)); + got + .post(url, { + json: { + chat_id: process.env.CHAT_ID, + text: message, + parse_mode: 'Markdown', + disable_notification: true, + }, + }) + .then(r => console.log(r.body)); } diff --git a/tests/change-page-mode.spec.ts b/tests/change-page-mode.spec.ts index 3f05f7df57..b48d19352f 100644 --- a/tests/change-page-mode.spec.ts +++ b/tests/change-page-mode.spec.ts @@ -4,17 +4,17 @@ import { loadPage } from './libs/load-page'; loadPage(); test.describe('Change page mode(Paper or Edgeless)', () => { - test('Switch to edgeless', async ({ page }) => { + test('Switch to edgeless by switch edgeless item', async ({ page }) => { const switcher = page.locator('[data-testid=editor-mode-switcher]'); const box = await switcher.boundingBox(); - await expect(box?.x).not.toBeUndefined(); + expect(box?.x).not.toBeUndefined(); - // mouse hover trigger animation for showing full switcher - await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); + // mouse hover trigger animation for showing full switcher + // await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); + await page.mouse.move((box?.x ?? 0) + 10, (box?.y ?? 0) + 10); - const edgelessButton = page.locator( - '[data-testid=editor-mode-switcher] [title=Edgeless]' - ); + // await page.waitForTimeout(1000); + const edgelessButton = page.getByTestId('switch-edgeless-item'); // page.getByText('Edgeless').click() await edgelessButton.click(); // // mouse move to edgeless button @@ -31,8 +31,20 @@ test.describe('Change page mode(Paper or Edgeless)', () => { // (box?.y ?? 0) + 5 // ); - const edgelessDom = page.locator('edgeless-page-block'); + const edgeless = page.locator('affine-edgeless-page'); + expect(await edgeless.isVisible()).toBe(true); + }); - await expect(await edgelessDom.isVisible()).toBe(true); + test.skip('Convert to edgeless by editor header items', async ({ page }) => { + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + const menusEdgelessItem = page.getByTestId('editor-option-menu-edgeless'); + await menusEdgelessItem.click(); + + const edgeless = page.locator('affine-edgeless-page'); + expect(await edgeless.isVisible()).toBe(true); }); }); diff --git a/tests/contact-us.spec.ts b/tests/contact-us.spec.ts index 531ee5d461..0de055787e 100644 --- a/tests/contact-us.spec.ts +++ b/tests/contact-us.spec.ts @@ -1,35 +1,35 @@ -import { test, expect, type Page } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import { loadPage } from './libs/load-page'; loadPage(); test.describe('Open contact us', () => { - test('Click left-top corner Logo', async ({ page }) => { - const leftTopCorner = page.locator('[data-testid=left-top-corner-logo]'); - await leftTopCorner.click(); - + test.skip('Click about us', async ({ page }) => { + const currentWorkspace = page.getByTestId('current-workspace'); + await currentWorkspace.click(); + // await page.waitForTimeout(1000); + await page.getByText('About AFFiNE').click(); const contactUsModal = page.locator( '[data-testid=contact-us-modal-content]' ); - await expect(contactUsModal).toContainText('Join our community.'); + await expect(contactUsModal).toContainText('AFFiNE Community'); }); - test('Click right-bottom corner contact icon', async ({ page }) => { const faqIcon = page.locator('[data-testid=faq-icon]'); const box = await faqIcon.boundingBox(); - await expect(box?.x).not.toBeUndefined(); - await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); + expect(box?.x).not.toBeUndefined(); + await page.mouse.move((box?.x ?? 0) + 10, (box?.y ?? 0) + 10); + await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); const rightBottomContactUs = page.locator( '[data-testid=right-bottom-contact-us-icon]' ); - await expect(await rightBottomContactUs.isVisible()).toEqual(true); + expect(await rightBottomContactUs.isVisible()).toEqual(true); await rightBottomContactUs.click(); - const contactUsModal = page.locator( '[data-testid=contact-us-modal-content]' ); - await expect(contactUsModal).toContainText('Join our community.'); + await expect(contactUsModal).toContainText('AFFiNE Community'); }); }); diff --git a/tests/exception-page.spec.ts b/tests/exception-page.spec.ts new file mode 100644 index 0000000000..5cf5ea9f4e --- /dev/null +++ b/tests/exception-page.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +// ps aux | grep 8080 +test.describe('exception page', () => { + test('visit 404 page', async ({ page }) => { + await page.goto('http://localhost:8080/404'); + await page.waitForTimeout(1000); + const notFoundTip = page.getByText('404 - Page Not Found'); + expect(await notFoundTip.isVisible()).toBe(true); + }); +}); diff --git a/tests/invite-code-page.spec.ts b/tests/invite-code-page.spec.ts new file mode 100644 index 0000000000..9cb7ba09ac --- /dev/null +++ b/tests/invite-code-page.spec.ts @@ -0,0 +1,13 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +// ps aux | grep 8080 +test.describe('invite code page', () => { + test('the link has expired', async ({ page }) => { + await page.goto('http://localhost:8080//invite/abc'); + await page.waitForTimeout(1000); + expect(page.getByText('The link has expired')).not.toBeUndefined(); + }); +}); diff --git a/tests/layout.spec.ts b/tests/layout.spec.ts new file mode 100644 index 0000000000..b817713871 --- /dev/null +++ b/tests/layout.spec.ts @@ -0,0 +1,21 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe('Layout ui', () => { + test('Collapse Sidebar', async ({ page }) => { + await page.getByTestId('sliderBar-arrowButton').click(); + const sliderBarArea = page.getByTestId('sliderBar'); + await expect(sliderBarArea).not.toBeVisible(); + }); + + test('Expand Sidebar', async ({ page }) => { + await page.getByTestId('sliderBar-arrowButton').click(); + const sliderBarArea = page.getByTestId('sliderBar'); + await expect(sliderBarArea).not.toBeVisible(); + + await page.getByTestId('sliderBar-arrowButton').click(); + await expect(sliderBarArea).toBeVisible(); + }); +}); diff --git a/tests/libs/keyboard.ts b/tests/libs/keyboard.ts new file mode 100644 index 0000000000..8da69c9818 --- /dev/null +++ b/tests/libs/keyboard.ts @@ -0,0 +1,65 @@ +import type { Page } from '@playwright/test'; + +const IS_MAC = process.platform === 'darwin'; + +async function keyDownCtrlOrMeta(page: Page) { + if (IS_MAC) { + await page.keyboard.down('Meta'); + } else { + await page.keyboard.down('Control'); + } +} + +async function keyUpCtrlOrMeta(page: Page) { + if (IS_MAC) { + await page.keyboard.up('Meta'); + } else { + await page.keyboard.up('Control'); + } +} + +// It's not good enough, but better than calling keyDownCtrlOrMeta and keyUpCtrlOrMeta separately +export const withCtrlOrMeta = async (page: Page, fn: () => Promise) => { + await keyDownCtrlOrMeta(page); + await fn(); + await keyUpCtrlOrMeta(page); +}; + +export async function pressEnter(page: Page) { + // avoid flaky test by simulate real user input + await page.keyboard.press('Enter', { delay: 50 }); +} + +export async function pressTab(page: Page) { + await page.keyboard.press('Tab', { delay: 50 }); +} + +export async function pressShiftTab(page: Page) { + await page.keyboard.down('Shift'); + await page.keyboard.press('Tab', { delay: 50 }); + await page.keyboard.up('Shift'); +} + +export async function pressShiftEnter(page: Page) { + await page.keyboard.down('Shift'); + await page.keyboard.press('Enter', { delay: 50 }); + await page.keyboard.up('Shift'); +} + +export async function copyByKeyboard(page: Page) { + await keyDownCtrlOrMeta(page); + await page.keyboard.press('c', { delay: 50 }); + await keyUpCtrlOrMeta(page); +} + +export async function cutByKeyboard(page: Page) { + await keyDownCtrlOrMeta(page); + await page.keyboard.press('x', { delay: 50 }); + await keyUpCtrlOrMeta(page); +} + +export async function pasteByKeyboard(page: Page) { + await keyDownCtrlOrMeta(page); + await page.keyboard.press('v', { delay: 50 }); + await keyUpCtrlOrMeta(page); +} diff --git a/tests/libs/load-page.ts b/tests/libs/load-page.ts index 400e7a000a..002341de65 100644 --- a/tests/libs/load-page.ts +++ b/tests/libs/load-page.ts @@ -2,8 +2,8 @@ import { test } from '@playwright/test'; export function loadPage() { test.beforeEach(async ({ page }) => { - await page.goto('http://localhost:3000'); + await page.goto('http://localhost:8080'); // waiting for page loading end - await page.waitForTimeout(1000); + // await page.waitForTimeout(1000); }); } diff --git a/tests/local-first-delete-page.spec.ts b/tests/local-first-delete-page.spec.ts new file mode 100644 index 0000000000..2e95d8b8d5 --- /dev/null +++ b/tests/local-first-delete-page.spec.ts @@ -0,0 +1,49 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe('Local first delete page', () => { + test.skip('New a page , then delete it in all pages, permanently delete it', async ({ + page, + }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to restore'); + const originPageUrl = page.url(); + const newPageId = page.url().split('/').reverse()[0]; + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to restore', + }); + expect(cell).not.toBeUndefined(); + + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + const deleteBtn = page.getByRole('button', { name: 'Delete' }); + await deleteBtn.click(); + const confirmTip = page.getByText('Delete page?'); + expect(confirmTip).not.toBeUndefined(); + + await page.getByRole('button', { name: 'Delete' }).click(); + + await page.getByRole('link', { name: 'Trash' }).click(); + // permanently delete it + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .nth(1) + .click(); + await page.getByText('Delete permanently?').dblclick(); + + // show empty tip + expect( + page.getByText( + 'Tips: Click Add to Favourites/Trash and the page will appear here.' + ) + ).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-export-page.spec.ts b/tests/local-first-export-page.spec.ts new file mode 100644 index 0000000000..71d9fd4bd6 --- /dev/null +++ b/tests/local-first-export-page.spec.ts @@ -0,0 +1,69 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first export page', () => { + test('New a page ,then open it and export html', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page + .getByPlaceholder('Title') + .fill('this is a new page to export html content'); + await page.getByRole('link', { name: 'All pages' }).click(); + + const cell = page.getByRole('cell', { + name: 'this is a new page to export html content', + }); + expect(cell).not.toBeUndefined(); + + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + const exportParentBtn = page.getByRole('tooltip', { + name: 'Add to favourites Convert to Edgeless Export Delete', + }); + await exportParentBtn.click(); + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.getByRole('button', { name: 'Export to HTML' }).click(), + ]); + expect(download.suggestedFilename()).toBe( + 'this is a new page to export html content.html' + ); + }); + + test('New a page ,then open it and export markdown', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page + .getByPlaceholder('Title') + .fill('this is a new page to export markdown content'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to export markdown content', + }); + expect(cell).not.toBeUndefined(); + + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + const exportParentBtn = page.getByRole('tooltip', { + name: 'Add to favourites Convert to Edgeless Export Delete', + }); + await exportParentBtn.click(); + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.getByRole('button', { name: 'Export to Markdown' }).click(), + ]); + expect(download.suggestedFilename()).toBe( + 'this is a new page to export markdown content.md' + ); + }); +}); diff --git a/tests/local-first-favorite-page.spec.ts b/tests/local-first-favorite-page.spec.ts new file mode 100644 index 0000000000..7a7aec1331 --- /dev/null +++ b/tests/local-first-favorite-page.spec.ts @@ -0,0 +1,72 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first favorite and cancel favorite page', () => { + test('New a page and open it ,then favorite it', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to favorite'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to favorite', + }); + expect(cell).not.toBeUndefined(); + + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); + await favoriteBtn.click(); + }); + test('Cancel favorite', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to favorite'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to favorite', + }); + expect(cell).not.toBeUndefined(); + + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + + const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); + await favoriteBtn.click(); + + // expect it in favorite list + await page.getByRole('link', { name: 'Favourites' }).click(); + expect( + page.getByRole('cell', { name: 'this is a new page to favorite' }) + ).not.toBeUndefined(); + + // cancel favorite + + await page.getByRole('link', { name: 'All pages' }).click(); + + const box = await page + .getByRole('cell', { name: 'this is a new page to favorite' }) + .boundingBox(); + //hover table record + await page.mouse.move((box?.x ?? 0) + 10, (box?.y ?? 0) + 10); + + await page.getByTestId('favourited-icon').click(); + + // expect it not in favorite list + await page.getByRole('link', { name: 'Favourites' }).click(); + expect( + page.getByText( + 'Tips: Click Add to Favourites/Trash and the page will appear here.' + ) + ).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-favorites-items.spec.ts b/tests/local-first-favorites-items.spec.ts new file mode 100644 index 0000000000..85e3d3c903 --- /dev/null +++ b/tests/local-first-favorites-items.spec.ts @@ -0,0 +1,58 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first favorite items ui', () => { + test('Show favorite items in sidebar', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to favorite'); + const newPageId = page.url().split('/').reverse()[0]; + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to favorite', + }); + expect(cell).not.toBeUndefined(); + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + + const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); + await favoriteBtn.click(); + const favoriteListItemInSidebar = page.getByTestId( + 'favorite-list-item-' + newPageId + ); + expect(await favoriteListItemInSidebar.textContent()).toBe( + 'this is a new page to favorite' + ); + }); + + test('Show favorite items in favorite list', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to favorite'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to favorite', + }); + expect(cell).not.toBeUndefined(); + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + + const favoriteBtn = page.getByTestId('editor-option-menu-favorite'); + await favoriteBtn.click(); + + await page.getByRole('link', { name: 'Favourites' }).click(); + expect( + page.getByRole('cell', { name: 'this is a new page to favorite' }) + ).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-new-page.spec.ts b/tests/local-first-new-page.spec.ts new file mode 100644 index 0000000000..a6753cbc2e --- /dev/null +++ b/tests/local-first-new-page.spec.ts @@ -0,0 +1,22 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('local first new page', () => { + test('click btn new page', async ({ page }) => { + const originPageId = page.url().split('/').reverse()[0]; + await page.getByText('New Page').click(); + const newPageId = page.url().split('/').reverse()[0]; + expect(newPageId).not.toBe(originPageId); + }); + + test('click btn bew page and find it in all pages', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { name: 'this is a new page' }); + expect(cell).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-openpage-newtab.spec.ts b/tests/local-first-openpage-newtab.spec.ts new file mode 100644 index 0000000000..b7e698345e --- /dev/null +++ b/tests/local-first-openpage-newtab.spec.ts @@ -0,0 +1,28 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('local first new page', () => { + test('click btn bew page and open in tab', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page'); + const newPageUrl = page.url(); + const newPageId = page.url().split('/').reverse()[0]; + + await page.getByRole('link', { name: 'All pages' }).click(); + + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + const [newTabPage] = await Promise.all([ + page.waitForEvent('popup'), + page.getByRole('button', { name: 'Open in new tab' }).click(), + ]); + + expect(newTabPage.url()).toBe(newPageUrl); + }); +}); diff --git a/tests/local-first-restore-page.spec.ts b/tests/local-first-restore-page.spec.ts new file mode 100644 index 0000000000..8b3d2ccae5 --- /dev/null +++ b/tests/local-first-restore-page.spec.ts @@ -0,0 +1,50 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe('Local first delete page', () => { + test('New a page , then delete it in all pages, restore it', async ({ + page, + }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to restore'); + const originPageUrl = page.url(); + const newPageId = page.url().split('/').reverse()[0]; + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to restore', + }); + expect(cell).not.toBeUndefined(); + + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + const deleteBtn = page.getByRole('button', { name: 'Delete' }); + await deleteBtn.click(); + const confirmTip = page.getByText('Delete page?'); + expect(confirmTip).not.toBeUndefined(); + + await page.getByRole('button', { name: 'Delete' }).click(); + + await page.getByRole('link', { name: 'Trash' }).click(); + // restore it + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + + // go to page detail + expect(page.url()).toBe(originPageUrl); + + await page.getByRole('link', { name: 'All pages' }).click(); + const restoreCell = page.getByRole('cell', { + name: 'this is a new page to restore', + }); + expect(restoreCell).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-show-delete-modal.spec.ts b/tests/local-first-show-delete-modal.spec.ts new file mode 100644 index 0000000000..a2cf040b7e --- /dev/null +++ b/tests/local-first-show-delete-modal.spec.ts @@ -0,0 +1,52 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first delete page', () => { + test('New a page ,then open it and show delete modal', async ({ page }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to delete'); + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to delete', + }); + expect(cell).not.toBeUndefined(); + + await cell.click(); + await page + .getByTestId('editor-header-items') + .getByRole('button') + .nth(2) + .click(); + const deleteBtn = page.getByTestId('editor-option-menu-delete'); + await deleteBtn.click(); + const confirmTip = page.getByText('Delete page?'); + expect(confirmTip).not.toBeUndefined(); + }); + + test('New a page ,then go to all pages and show delete modal', async ({ + page, + }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to delete'); + const newPageId = page.url().split('/').reverse()[0]; + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to delete', + }); + expect(cell).not.toBeUndefined(); + + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + const deleteBtn = page.getByRole('button', { name: 'Delete' }); + await deleteBtn.click(); + const confirmTip = page.getByText('Delete page?'); + expect(confirmTip).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-trash-page.spec.ts b/tests/local-first-trash-page.spec.ts new file mode 100644 index 0000000000..a25c4696b8 --- /dev/null +++ b/tests/local-first-trash-page.spec.ts @@ -0,0 +1,37 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first trash page', () => { + test('New a page , then delete it in all pages, finally find it in trash', async ({ + page, + }) => { + await page.getByText('New Page').click(); + await page.getByPlaceholder('Title').click(); + await page.getByPlaceholder('Title').fill('this is a new page to delete'); + const newPageId = page.url().split('/').reverse()[0]; + await page.getByRole('link', { name: 'All pages' }).click(); + const cell = page.getByRole('cell', { + name: 'this is a new page to delete', + }); + expect(cell).not.toBeUndefined(); + + await page + .getByTestId('more-actions-' + newPageId) + .getByRole('button') + .first() + .click(); + const deleteBtn = page.getByRole('button', { name: 'Delete' }); + await deleteBtn.click(); + const confirmTip = page.getByText('Delete page?'); + expect(confirmTip).not.toBeUndefined(); + + await page.getByRole('button', { name: 'Delete' }).click(); + + await page.getByRole('link', { name: 'Trash' }).click(); + expect( + page.getByRole('cell', { name: 'this is a new page to delete' }) + ).not.toBeUndefined(); + }); +}); diff --git a/tests/local-first-workspace.spec.ts b/tests/local-first-workspace.spec.ts new file mode 100644 index 0000000000..6aaf66786b --- /dev/null +++ b/tests/local-first-workspace.spec.ts @@ -0,0 +1,18 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Local first default workspace', () => { + test('Default workspace name', async ({ page }) => { + const workspaceName = page.getByTestId('workspace-name'); + expect(await workspaceName.textContent()).toBe('AFFiNE'); + }); + + test('Default workspace avatar', async ({ page }) => { + const workspaceAvatar = page.getByTestId('workspace-avatar'); + expect(await workspaceAvatar.innerHTML()).toBe( + '' + ); + }); +}); diff --git a/tests/login.spec.ts b/tests/login.spec.ts new file mode 100644 index 0000000000..24e45092f5 --- /dev/null +++ b/tests/login.spec.ts @@ -0,0 +1,44 @@ +import { test, expect } from '@playwright/test'; +import { loadPage } from './libs/load-page'; + +loadPage(); + +test.describe.skip('Login Flow', () => { + test('Open login modal by click current workspace', async ({ page }) => { + await page.getByTestId('current-workspace').click(); + await page.waitForTimeout(800); + // why don't we use waitForSelector, It seems that waitForSelector not stable? + await page.getByTestId('open-login-modal').click(); + await page.waitForTimeout(800); + await page + .getByRole('heading', { name: 'Currently not logged in' }) + .click(); + }); + + test('Open login modal by click cloud-unsync-icon', async ({ page }) => { + await page.getByTestId('cloud-unsync-icon').click(); + + await page.waitForTimeout(800); + await page + .getByRole('heading', { name: 'Currently not logged in' }) + .click(); + }); + + test('Open google firebase page', async ({ page }) => { + await page.getByTestId('current-workspace').click(); + await page.waitForTimeout(800); + // why don't we use waitForSelector, It seems that waitForSelector not stable? + await page.getByTestId('open-login-modal').click(); + await page.waitForTimeout(800); + const [firebasePage] = await Promise.all([ + page.waitForEvent('popup'), + page + .getByRole('button', { + name: 'Google Continue with Google Set up an AFFiNE account to sync data', + }) + .click(), + ]); + + expect(firebasePage.url()).toContain('.firebaseapp.com/__/auth/handler'); + }); +}); diff --git a/tests/quick-search.spec.ts b/tests/quick-search.spec.ts new file mode 100644 index 0000000000..8fdd515889 --- /dev/null +++ b/tests/quick-search.spec.ts @@ -0,0 +1,108 @@ +import { test, expect, type Page } from '@playwright/test'; +import { loadPage } from './libs/load-page'; +import { withCtrlOrMeta } from './libs/keyboard'; + +loadPage(); + +const openQuickSearchByShortcut = async (page: Page) => + await withCtrlOrMeta(page, () => page.keyboard.press('k', { delay: 50 })); + +async function assertTitleTexts( + page: Page, + texts: string[], + option?: { delay: number } +) { + await page.mouse.move(100, 100); + const actual = await page + .locator('[class=affine-default-page-block-title]') + .allInnerTexts(); + await page.waitForTimeout(option?.delay || 0); + + expect(actual).toEqual(texts); +} +async function assertResultList( + page: Page, + texts: string[], + option?: { delay: number } +) { + await page.mouse.move(100, 100); + const actual = await page.locator('[cmdk-item]').allInnerTexts(); + await page.waitForTimeout(option?.delay || 0); + expect(actual).toEqual(texts); +} + +test.describe('Open quick search', () => { + test('Click slider bar button', async ({ page }) => { + const quickSearchButton = page.locator( + '[data-testid=sliderBar-quickSearchButton]' + ); + await quickSearchButton.click(); + const quickSearch = page.locator('[data-testid=quickSearch]'); + await expect(quickSearch).toBeVisible(); + }); + + test('Click arrowDown icon after title', async ({ page }) => { + //header-quickSearchButton + const quickSearchButton = page.locator( + '[data-testid=header-quickSearchButton]' + ); + await quickSearchButton.click(); + const quickSearch = page.locator('[data-testid=quickSearch]'); + await expect(quickSearch).toBeVisible(); + }); + + // test('Press the shortcut key cmd+k', async ({ page }) => { + // // why 1000ms page wait? + // page.waitForTimeout(1000); + // await openQuickSearchByShortcut(page); + // const quickSearch = page.locator('[data-testid=quickSearch]'); + + // await expect(quickSearch).toBeVisible(); + // }); +}); + +test.describe('Add new page in quick search', () => { + // FIXME: not working + test.skip('Create a new page without keyword', async ({ page }) => { + await openQuickSearchByShortcut(page); + const addNewPage = page.locator('[data-testid=quickSearch-addNewPage]'); + await addNewPage.click(); + await assertTitleTexts(page, [''], { delay: 50 }); + }); + + test.skip('Create a new page with keyword', async ({ page }) => { + await openQuickSearchByShortcut(page); + await page.keyboard.insertText('test'); + const addNewPage = page.locator('[data-testid=quickSearch-addNewPage]'); + await addNewPage.click(); + await assertTitleTexts(page, ['test'], { delay: 50 }); + }); +}); + +test.describe('Search and select', () => { + // test('Search and get results', async ({ page }) => { + // // why 1000ms page wait? + // await openQuickSearchByShortcut(page); + // await page.keyboard.insertText('Welcome'); + // await assertResultList(page, ['Welcome to the AFFiNE Alpha'], { + // delay: 50, + // }); + // }); + //TODO FIXME: This test is not working + test.skip('Create a new page and search this page', async ({ page }) => { + await openQuickSearchByShortcut(page); + await page.keyboard.insertText('Welcome'); + const addNewPage = page.locator('[data-testid=quickSearch-addNewPage]'); + await addNewPage.click(); + await page.waitForTimeout(500); + await openQuickSearchByShortcut(page); + await page.keyboard.insertText('Welcome'); + await assertResultList(page, ['Welcome to the AFFiNE Alpha', 'Welcome']); + + await page.keyboard.press('ArrowDown', { delay: 50 }); + await page.keyboard.press('Enter', { delay: 50 }); + await assertTitleTexts(page, ['Welcome'], { + delay: 50, + }); + }); +}); diff --git a/tests/shortcuts.spec.ts b/tests/shortcuts.spec.ts index e6f353f892..f8546eeb02 100644 --- a/tests/shortcuts.spec.ts +++ b/tests/shortcuts.spec.ts @@ -7,14 +7,14 @@ test.describe('Shortcuts Modal', () => { test('Open shortcuts modal', async ({ page }) => { const faqIcon = page.locator('[data-testid=faq-icon]'); const box = await faqIcon.boundingBox(); - await expect(box?.x).not.toBeUndefined(); + expect(box?.x).not.toBeUndefined(); await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); const shortcutsIcon = page.locator('[data-testid=shortcuts-icon]'); - await expect(await shortcutsIcon.isVisible()).toEqual(true); + expect(await shortcutsIcon.isVisible()).toEqual(true); await shortcutsIcon.click(); - + await page.waitForTimeout(800); const shortcutsModal = page.locator('[data-testid=shortcuts-modal]'); await expect(shortcutsModal).toContainText('Keyboard Shortcuts'); }); diff --git a/tests/theme.spec.ts b/tests/theme.spec.ts index 3b6cb6cd76..b92514860b 100644 --- a/tests/theme.spec.ts +++ b/tests/theme.spec.ts @@ -1,22 +1,22 @@ -import { test, expect, type Page } from '@playwright/test'; +import { test, expect } from '@playwright/test'; import { loadPage } from './libs/load-page'; loadPage(); test.describe('Change Theme', () => { test('default white', async ({ page }) => { + await page.waitForSelector('html'); const root = page.locator('html'); const themeMode = await root.evaluate(element => window.getComputedStyle(element).getPropertyValue('--affine-theme-mode') ); - - await expect(themeMode).toBe('light'); + expect(themeMode).toBe('light'); const lightButton = page.locator('[data-testid=change-theme-light]'); const buttonPositionTop = await lightButton.evaluate( element => window.getComputedStyle(element).top ); - await expect(buttonPositionTop).toBe('0px'); + expect(buttonPositionTop).toBe('0px'); }); test('change theme to dark', async ({ page }) => { @@ -24,24 +24,21 @@ test.describe('Change Theme', () => { '[data-testid=change-theme-container]' ); const box = await changeThemeContainer.boundingBox(); - await expect(box?.x).not.toBeUndefined(); + expect(box?.x).not.toBeUndefined(); + await page.mouse.move((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); - await page.waitForTimeout(1000); - const darkButton = page.locator('[data-testid=change-theme-dark]'); const darkButtonPositionTop = await darkButton.evaluate( element => element.getBoundingClientRect().y ); - await expect(darkButtonPositionTop).toBe(box?.y); + expect(darkButtonPositionTop).toBe(box?.y); await page.mouse.click((box?.x ?? 0) + 5, (box?.y ?? 0) + 5); - const root = page.locator('html'); const themeMode = await root.evaluate(element => window.getComputedStyle(element).getPropertyValue('--affine-theme-mode') ); - - await expect(themeMode).toBe('dark'); + expect(themeMode).toBe('dark'); }); }); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000..592edefeb6 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "experimentalDecorators": true, + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +}