diff --git a/.all-contributorsrc b/.all-contributorsrc index b08412fb49..74bef4b2b9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -436,6 +436,33 @@ "contributions": [ "code" ] + }, + { + "login": "hezhizhen", + "name": "Zhizhen He", + "avatar_url": "https://avatars.githubusercontent.com/u/7611700?v=4", + "profile": "https://t.me/littlepoint", + "contributions": [ + "code" + ] + }, + { + "login": "AkaraChen", + "name": "AkaraChen", + "avatar_url": "https://avatars.githubusercontent.com/u/85140972?v=4", + "profile": "https://akr.moe/", + "contributions": [ + "code" + ] + }, + { + "login": "suyanhanx", + "name": "Suyan", + "avatar_url": "https://avatars.githubusercontent.com/u/24221472?v=4", + "profile": "https://github.com/suyanhanx", + "contributions": [ + "code" + ] } ] } diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 4e1862166b..0000000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -**/webpack.config.js -**/scripts/*.js -**/node_modules/** -.github/** -**/__tests__/** -**/tests/** - diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000000..f3c1752f5c --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ + diff --git a/.github/ISSUE_TEMPLATE/.gitkeep b/.github/ISSUE_TEMPLATE/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/.github/ISSUE_TEMPLATE/bug-report-alpha.yml b/.github/ISSUE_TEMPLATE/bug-report-alpha.yml deleted file mode 100644 index 599cf75af2..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-report-alpha.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: 🐛 Bug report (Alpha) -description: "Report a reproducible bug or regression for https://pathfinder.affine.pro" -title: "[bug]: " -labels: ["bug", "alpha"] -body: - - type: markdown - attributes: - value: Thanks for taking the time to fill out this bug report! - - type: input - id: description - attributes: - label: Describe the bug - placeholder: A clear and concise description of what the bug is. - - type: textarea - id: reproduce - attributes: - label: To Reproduce - placeholder: "Steps to reproduce the behavior\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error" - validations: - required: true - - type: textarea - id: screenshots - attributes: - label: Screenshots - placeholder: If applicable, add screenshots to help explain your problem. - - type: textarea - id: expected - attributes: - label: Expected behavior - placeholder: A clear and concise description of what you expected to happen. - - type: input - id: platform - attributes: - label: Platform - placeholder: e.g. MacOS, Windows10... - - type: input - id: browser - attributes: - label: Browser - placeholder: e.g. Chrome, Safari - - type: textarea - id: additional - attributes: - label: Additional context - placeholder: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml b/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml deleted file mode 100644 index a75e3db6cd..0000000000 --- a/.github/ISSUE_TEMPLATE/bug-report-pre-alpha.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: 🐛 Bug report (Pre-Alpha) -description: "Report a reproducible bug or regression for https://livedemo.affine.pro" -title: "[bug]: " -labels: ["bug", "pre-alpha"] -body: - - type: markdown - attributes: - value: Thanks for taking the time to fill out this bug report! - - type: input - id: description - attributes: - label: Describe the bug - placeholder: A clear and concise description of what the bug is. - - type: textarea - id: reproduce - attributes: - label: To Reproduce - placeholder: "Steps to reproduce the behavior\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error" - validations: - required: true - - type: textarea - id: screenshots - attributes: - label: Screenshots - placeholder: If applicable, add screenshots to help explain your problem. - - type: textarea - id: expected - attributes: - label: Expected behavior - placeholder: A clear and concise description of what you expected to happen. - - type: input - id: platform - attributes: - label: Platform - placeholder: e.g. MacOS, Windows10... - - type: input - id: browser - attributes: - label: Browser - placeholder: e.g. Chrome, Safari - - type: textarea - id: additional - attributes: - label: Additional context - placeholder: Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index a7505c86e0..0000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,11 +0,0 @@ -blank_issues_enabled: true -contact_links: - - name: 💭 Questions and Help - Reddit - url: https://www.reddit.com/r/Affine/ - about: Please ask and answer questions here. - - name: 💬 Questions and Help - Telegram - url: https://t.me/affineworkos - about: Please ask and answer questions here. - - name: 🗯 Questions and Help - Discord - url: https://discord.gg/yz6tGVsf5p - about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 54c1caa4f3..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: ✨ Feature request -description: An idea or request for new functionality -title: "[feature]: " -labels: ["enhancement"] -body: - - type: markdown - attributes: - value: Thanks for taking the time to fill out this feature request! - - type: textarea - id: description - attributes: - label: 1~3 main use cases of the proposed feature - description: e.g. As a ..., I have many tasks scattered across documents, and I want to have a unified entry to view these tasks. - placeholder: e.g. As a ... - validations: - required: true - - type: textarea - id: solution - attributes: - label: Ideas for solution - placeholder: e.g. A task view can be added to view all tasks. - - type: input - id: userType - attributes: - label: what types of users can benefit from using your proposed feature - placeholder: busy student - - type: textarea - id: additional - attributes: - label: Additional context - placeholder: Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/improvement-request.yml b/.github/ISSUE_TEMPLATE/improvement-request.yml deleted file mode 100644 index 769e679131..0000000000 --- a/.github/ISSUE_TEMPLATE/improvement-request.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: 🪄 Improvement request -description: An improvement to existing functionality -title: "[improvement]: " -labels: ["improvement"] -body: - - type: markdown - attributes: - value: Thanks for taking the time to fill out this improvement request! - - type: textarea - id: description - attributes: - label: 1~3 main use cases of the proposed improvement - description: e.g. As a ..., I have many tasks scattered across documents, and I want to have a unified entry to view these tasks. - placeholder: e.g. As a ... - validations: - required: true - - type: textarea - id: solution - attributes: - label: Ideas for solution - placeholder: e.g. A task view can be added to view all tasks. - - type: input - id: userType - attributes: - label: what types of users can benefit from using your proposed improvement - placeholder: busy student - - type: textarea - id: additional - attributes: - label: Additional context - placeholder: Add any other context or screenshots about the improvement request here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 3bb05d3646..0000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: I have a question -about: Feel free to ask us your questions! -title: '[Question]' -labels: '' -assignees: '' ---- diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d0c262639..a41c84f4c3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,18 +67,16 @@ jobs: lint: name: Lint and E2E Test - runs-on: ubuntu-latest + runs-on: self-hosted environment: development needs: build steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: pnpm/action-setup@v2 with: version: 'latest' - - - name: Use Node.js - uses: actions/setup-node@v2 + - uses: actions/setup-node@v3 with: node-version: 18.x cache: 'pnpm' @@ -96,8 +94,6 @@ jobs: - name: Install dependencies run: pnpm install - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - name: Download artifact uses: actions/download-artifact@v3 @@ -105,20 +101,40 @@ jobs: name: artifact path: packages/app/.next/ - - name: Lint & E2E Test + - name: Lint & E2E Test & Unit 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 }} + npx playwright install chromium + pnpm run test:coverage + pnpm run test:unit + # 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: Collect code coverage report + run: pnpm exec nyc report -t .nyc_output --report-dir .coverage --reporter=lcov + + - name: Upload e2e test coverage results + uses: codecov/codecov-action@v3 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./.coverage/lcov.info + flags: e2etest + name: affine + fail_ci_if_error: true + + - name: Upload test results + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: test-results-main + path: ./test-results + if-no-files-found: ignore build-community: name: Build Community diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index 00ce55b51c..0000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Unit Tests -on: - push: - branches: [master] - pull_request: - branches: [master] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - uses: pnpm/action-setup@v2 - with: - version: 'latest' - - - uses: actions/setup-node@v3 - with: - node-version: 16 - registry-url: https://npm.pkg.github.com - scope: '@toeverything' - cache: 'pnpm' - - - run: node scripts/module-resolve/ci.cjs - - - name: Install dependencies - run: pnpm install --no-frozen-lockfile - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_GITHUB_AUTH_TOKEN }} - - - name: Install Playwright browsers - run: npx playwright install chromium - - # - 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 f9333264dc..090865ce93 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *dist /tmp /out-tsc +.nyc_output +.coverage # dependencies node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json index 83567e87ef..9045fac983 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,7 +19,9 @@ "explorer.fileNesting.expand": false, "explorer.fileNesting.patterns": { "*.js": "${capture}.js.map, ${capture}.min.js, ${capture}.d.ts, ${capture}.d.ts.map", - "package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*" + "package.json": ".browserslist*, .circleci*, .codecov, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lighthouserc.*, .lintstagedrc*, .markdownlint*, .mocha*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, api-extractor.json, apollo.config.*, appveyor*, ava.config.*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, cypress.*, dangerfile*, dlint.json, dprint.json, firebase.json, grunt*, gulp*, histoire.config.*, jasmine.*, jenkins*, jest.config.*, jsconfig.*, karma*, lerna*, lighthouserc.*, lint-staged*, nest-cli.*, netlify*, nodemon*, nx.*, package-lock.json, package.nls*.json, phpcs.xml, playwright.config.*, pm2.*, pnpm*, prettier*, pullapprove*, puppeteer.config.*, pyrightconfig.json, release-tasks.sh, renovate*, rollup.config.*, stylelint*, tsconfig.*, tsdoc.*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, vitest.config.*, webpack*, workspace.json, xo.config.*, yarn*, babel.*, .babelrc, project.json", + "Cargo.toml": "Cargo.lock", + "README.md": "LICENSE, CHANGELOG.md, CODE_OF_CONDUCT.md, CONTRIBUTING.md" }, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 2e9c0db51e..0000000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,45 +0,0 @@ -# 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 deleted file mode 100644 index 3876d1b89b..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,3 +0,0 @@ -# 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/README.md b/README.md index 97c5d04bc4..dc750f48df 100644 --- a/README.md +++ b/README.md @@ -18,13 +18,14 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066 --> -[all-contributors-badge]: https://img.shields.io/badge/all_contributors-45-orange.svg?style=flat-square +[all-contributors-badge]: https://img.shields.io/badge/all_contributors-48-orange.svg?style=flat-square [![affine.pro](https://img.shields.io/static/v1?label=live%20demo&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhpJREFUWEdjZEACtnl3MxgY/0YzMjAaMzAwcCLLUYH9/T/D/7MM/5mXHp6kPANmHiOI4Zx9Xfg3C+tKBob/zlSwiAgjGPey/vkdvneq5luwA+zy7+yhn+Vwv+89NFHFhREU7IyM/6YT4WyqK/n/nymT0Tb/1mFGBkYbqptOhIH/Gf4fYbTLv/2NBgmOCOvBSr6DHPCfWNW0UEe2A2x1uRlakiXBbtpx6jND+7KXZLmPbAdURokzeJjxwi31rrzH8OX7P5IdQbYDtnUoMXBzMMEt7Fj2imH7qU/0cQBy8MNsPHL5K0P13Of0cQB68MNsJScaSI4CHk4mhq3tSnCf3n36k0FZmh3Mn7L+DcPqgx9ICgWSHeBpxsdQESUGtgRk+eqDH+H8O09/MiR3P6atA1qTJRlsdLnhPgYlPOQQCW96wPDi3R+iHUFSCKAHP8wydEeREg0kOQA9+JOgwR1qL8CQEygC9jWp0UCSA+aVysIT3JqDHxgmr38DtlRCiIVhZZ0CPNhB6QDkEGIA0Q4gZAkuxxFyBNEOQA7ml+/+MIQ1PUAxG1kelAhB6YMYQLQDCPmQUAjhcgxRDiDWcEKOxOYIohyQGyjCEGIvANaPLfhhBiNHA6hmBBXNhABRDgCV/aBQAAFQpYMrn4PUgNTCACiXEMoNRDmAkC8okR8UDhjYRumAN8sHvGMCSkAD2jUDOWDAO6ewbDQQ3XMAy/oxKownQR0AAAAASUVORK5CYII=&color=orange&message=→)](https://livedemo.affine.pro) [![stars](https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars)](https://github.com/toeverything/AFFiNE) [![All Contributors][all-contributors-badge]](#contributors) +![codecov](https://codecov.io/gh/toeverything/affine/branch/master/graphs/badge.svg?branch=master) [![Node](https://img.shields.io/badge/node->=16.0-success)](https://www.typescriptlang.org/) [![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/) @@ -80,9 +81,9 @@ Before we tell you how to get started with AFFiNE, we'd like to shamelessly plug [![affine.pro](https://img.shields.io/static/v1?label=Try%20it%20Online&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhpJREFUWEdjZEACtnl3MxgY/0YzMjAaMzAwcCLLUYH9/T/D/7MM/5mXHp6kPANmHiOI4Zx9Xfg3C+tKBob/zlSwiAgjGPey/vkdvneq5luwA+zy7+yhn+Vwv+89NFHFhREU7IyM/6YT4WyqK/n/nymT0Tb/1mFGBkYbqptOhIH/Gf4fYbTLv/2NBgmOCOvBSr6DHPCfWNW0UEe2A2x1uRlakiXBbtpx6jND+7KXZLmPbAdURokzeJjxwi31rrzH8OX7P5IdQbYDtnUoMXBzMMEt7Fj2imH7qU/0cQBy8MNsPHL5K0P13Of0cQB68MNsJScaSI4CHk4mhq3tSnCf3n36k0FZmh3Mn7L+DcPqgx9ICgWSHeBpxsdQESUGtgRk+eqDH+H8O09/MiR3P6atA1qTJRlsdLnhPgYlPOQQCW96wPDi3R+iHUFSCKAHP8wydEeREg0kOQA9+JOgwR1qL8CQEygC9jWp0UCSA+aVysIT3JqDHxgmr38DtlRCiIVhZZ0CPNhB6QDkEGIA0Q4gZAkuxxFyBNEOQA7ml+/+MIQ1PUAxG1kelAhB6YMYQLQDCPmQUAjhcgxRDiDWcEKOxOYIohyQGyjCEGIvANaPLfhhBiNHA6hmBBXNhABRDgCV/aBQAAFQpYMrn4PUgNTCACiXEMoNRDmAkC8okR8UDhjYRumAN8sHvGMCSkAD2jUDOWDAO6ewbDQQ3XMAy/oxKownQR0AAAAASUVORK5CYII=&message=%E2%86%92&style=for-the-badge)](https://affine.pro) No installation or registration required! Head over to our website and try it out now. -[AFFiNE Documentation](https://docs.affine.pro/affine/) - More detailed documentation on how to use and develop with AFFiNE +[AFFiNE Community](https://community.affine.pro) - Our wonderful community, where you can meet and engage with the team, developers and other like-minded enthusiastic user of AFFiNE. -[Our official communities](https://docs.affine.pro/affine/community-links/official-communities) - Join our friendly communities for more support and discussions +[Our official communities](https://community.affine.pro/c/start-here/) - Join our friendly communities for more support and discussions. ## Contributing @@ -90,19 +91,18 @@ Calling all developers, testers, tech writers and more! Contributions of all typ For **bug reports**, **feature requests** and other **suggestions** you can also [create a new issue](https://github.com/toeverything/AFFiNE/issues/new/choose) and choose the most appropiate template for your feedback. -For **translation** and **language support** you can visit our docs for the [internationalization guide](https://docs.affine.pro/affine/internationalization/welcome). +For **translation** and **language support** you can visit our [i18n General Space](https://community.affine.pro/c/i18n-general). -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://community.affine.pro/c/start-here/affine-ambassador), we work closely with passionate community members 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. - -We have done a major refactoring recently, if you want to see our previous version of the code, please go to the [Pre-Alpha](https://github.com/toeverything/AFFiNE/tree/Pre-Alpha) branch to view +If you have questions, you are welcome to contact us. One of the best places to get more info and learn more is in the [AFFiNE Community](https://community.affine.pro) where you can engage with other like-minded individuals. ## Thanks 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. +- [BlockSuite](https://github.com/toeverything/BlockSuite) - 💠 BlockSuite is the open-source collaborative editor project behind AFFiNE. +- [OctoBase](https://github.com/toeverything/OctoBase) - 🐙 OctoBase is the open-source database behind AFFiNE, local-first, yet collaborative. A light-weight, scalable, data engine written in Rust. - [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, OctoBase. @@ -184,6 +184,9 @@ Thanks a lot to the community for providing such powerful and simple libraries,
Mohammed Faraz

📖
Pranav Sriram

💻
Reson-a

💻 +
Zhizhen He

💻 +
AkaraChen

💻 +
Suyan

💻 @@ -192,10 +195,26 @@ Thanks a lot to the community for providing such powerful and simple libraries, -## Jobs +## Self-Host + +Get started with Docker and deploy your own feature-rich, restriction-free deployment of AFFiNE - check the [latest packages](https://github.com/toeverything/AFFiNE/pkgs/container/affine-self-hosted). + +## Hiring Some amazing companies including AFFiNE are looking for developers! Are you interested in helping build with AFFiNE and/or its partners? Check out some of the latest [jobs available](./docs/jobs/summary.md). +## Upgrading + +For upgrading information please see our [update page](https://affine.pro/blog?tag=Release%20Note). + +## Feature Request + +For feature request please see https://community.affine.pro/c/feature-requests/ + +## Is it awesome? + +[These people](https://twitter.com/AffineOfficial/followers) seem to like it. + ## License See [LICENSE](/LICENSE) for details. diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md index de5d75c159..2e9c0db51e 100644 --- a/docs/CODE_OF_CONDUCT.md +++ b/docs/CODE_OF_CONDUCT.md @@ -8,19 +8,19 @@ In the interest of fostering an open and welcoming environment, we as contributo 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 +- 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 +- 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 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index c278df5d3d..fa4a4f4d77 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -12,8 +12,8 @@ Use the table of contents icon on the top left corner of this document to get to Currently we have two versions of AFFiNE: -- [AFFiNE Pre-Alpha](https://livedemo.affine.pro/). This version users the branch `master`, however is no longer actively developed and will be archived in the future. -- [AFFiNE Alpha](https://pathfinder.affine.pro/). This version uses the 'pathfinder' branch, this is the latest version under active development. We plan to update this to the master branch in the near future. +- [AFFiNE Pre-Alpha](https://livedemo.affine.pro/). This version users the branch `Pre-Alpha`, it is no longer actively developed but contains some different functions and features. +- [AFFiNE Alpha](https://pathfinder.affine.pro/). This version uses the `master` branch, this is the latest version under active development. To get an overview of the project, read the [README](../README.md). Here are some resources to help you get started with open source contributions: diff --git a/docs/contributing/bump-blocksuite.md b/docs/contributing/bump-blocksuite.md new file mode 100644 index 0000000000..5558cbdd8a --- /dev/null +++ b/docs/contributing/bump-blocksuite.md @@ -0,0 +1,32 @@ +# Bump Blocksuite + +```shell +./scripts/upgrade-blocksuite.sh +``` + +## Understand the version number + +### Stable + +You can see all the stable version tags [here](https://github.com/toeverything/blocksuite/tags). + +### Nightly + +If it's nightly version, the version will follow `${version}-${date}-${hash}`. + +For example, `0.4.0-20230203030233-b22bea7` means that +the version is based on `0.4.0`, the building date is `20230203030233`, +and the commit hash is `b22bea7`. + +> For the source code, see [here](https://github.com/toeverything/set-build-version/blob/master/src/version.ts) + +Using this version format, you can easily check the diff between each version. + +For example, the diff from old version `0.4.0-20230201063624-4e0463b` to new version `0.4.0-20230203030233-b22bea7` +is . + +## Fix the breaking change + +You can follow this file to keep updated: + +https://github.com/toeverything/blocksuite/blob/master/packages/playground/src/main.ts diff --git a/docs/contributor-add.md b/docs/contributor-add.md index 5cc07bd3ce..67768e8095 100644 --- a/docs/contributor-add.md +++ b/docs/contributor-add.md @@ -4,6 +4,7 @@ - https://allcontributors.org/docs/en/emoji-key ```shell +all-contributors check all-contributors add tzhangchi code,doc all-contributors generate ``` diff --git a/docs/types-of-contributions.md b/docs/types-of-contributions.md index 9e2a647a0f..14ececb849 100644 --- a/docs/types-of-contributions.md +++ b/docs/types-of-contributions.md @@ -26,4 +26,4 @@ You may be able to find additional help and information on our social media plat ### :earth_asia: Translations -AFFiNE is internationalized and available in multiple languages. The source content in this repository is written in English. We integrate with an external localization platform to work with the community in localizing the English content. You can find more info in our [internationalization docs](https://docs.affine.pro/affine/internationalization/welcome). +AFFiNE is internationalized and available in multiple languages. The source content in this repository is written in English. We integrate with an external localization platform to work with the community in localizing the English content. You can find more info on our community page, in our [i18n General Space ](https://community.affine.pro/c/i18n-general). diff --git a/package.json b/package.json index 9ba3fb02b0..70b762cf6d 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,10 @@ "start": "pnpm --filter @affine/app start", "lint": "pnpm --filter @affine/app lint", "test": "playwright test", - "test:dc": "pnpm --filter @affine/datacenter test", - "test:e2e:codegen": "npx playwright codegen http://localhost:8080", + "test:coverage": "cross-env COVERAGE=true pnpm test -- --forbid-only", "test:unit": "playwright test --config=playwright.config.unit.ts", "postinstall": "husky install", "notify": "node --experimental-modules scripts/notify.mjs", - "check:ci": "pnpm lint & pnpm test", "update:blocksuite": "pnpm i --filter @affine/app --filter @affine/datacenter @blocksuite/blocks@nightly @blocksuite/store@nightly && pnpm i --filter @affine/app @blocksuite/editor@nightly" }, "lint-staged": { @@ -29,13 +27,13 @@ }, "devDependencies": { "@changesets/cli": "^2.26.0", - "@jest/globals": "^29.3.1", "@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", "concurrently": "^7.6.0", + "babel-plugin-istanbul": "^6.1.1", "cross-env": "^7.0.3", "eslint": "^8.30.0", "eslint-config-next": "12.3.1", @@ -44,10 +42,10 @@ "fake-indexeddb": "4.0.1", "got": "^12.5.3", "husky": "^8.0.2", - "jest": "^29.3.1", "lint-staged": "^13.1.0", + "nyc": "^15.1.0", "prettier": "^2.7.1", - "typescript": "^4.9.3" + "typescript": "^4.9.5" }, "eslintConfig": { "root": true, @@ -70,9 +68,16 @@ }, "reportUnusedDisableDirectives": true, "ignorePatterns": [ - "src/**/*.test.ts", "package/**/dist/*", - "package/**/sync.js" + "package/**/.babelrc.js", + "package/**/sync.js", + "src/**/*.test.ts", + "**/webpack.config.js", + "**/scripts/*.js", + "**/node_modules/**", + ".github/**", + "**/__tests__/**", + "**/tests/**" ] } } diff --git a/packages/app/.babelrc.js b/packages/app/.babelrc.js new file mode 100644 index 0000000000..e98989a64f --- /dev/null +++ b/packages/app/.babelrc.js @@ -0,0 +1,28 @@ +const plugins = []; + +if (process.env.NODE_ENV === 'development' || process.env.COVERAGE === 'true') { + console.log( + 'Detected development environment. Instrumenting code for coverage.' + ); + plugins.push('istanbul'); +} + +plugins.push([ + '@emotion', + { + // See https://emotion.sh/docs/@emotion/babel-plugin + importMap: { + '@/styles': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: ['@/styles', 'styled'], + }, + }, + }, + }, +]); + +module.exports = { + presets: ['next/babel'], + plugins, +}; diff --git a/packages/app/next.config.js b/packages/app/next.config.js index 2e54ff005a..1ef34b267f 100644 --- a/packages/app/next.config.js +++ b/packages/app/next.config.js @@ -68,6 +68,9 @@ const nextConfig = { return profile; }, basePath: process.env.BASE_PATH, + experimental: { + forceSwcTransforms: true, + }, }; const baseDir = process.env.LOCAL_BLOCK_SUITE ?? '/'; @@ -98,6 +101,7 @@ const withDebugLocal = require('next-debug-local')( const withPWA = require('next-pwa')({ dest: 'public', + scope: '/_next', disable: process.env.NODE_ENV !== 'production', }); diff --git a/packages/app/package.json b/packages/app/package.json index 31fa964621..cf19c695c9 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -11,10 +11,10 @@ "dependencies": { "@affine/datacenter": "workspace:*", "@affine/i18n": "workspace:*", - "@blocksuite/blocks": "0.4.0-20230111171650-bc63456", - "@blocksuite/editor": "0.4.0-20230111171650-bc63456", + "@blocksuite/blocks": "0.4.0-alpha.2", + "@blocksuite/editor": "0.4.0-alpha.2", "@blocksuite/icons": "^2.0.2", - "@blocksuite/store": "0.4.0-20230111171650-bc63456", + "@blocksuite/store": "0.4.0-alpha.2", "@emotion/css": "^11.10.0", "@emotion/react": "^11.10.4", "@emotion/server": "^11.10.0", @@ -28,7 +28,7 @@ "cmdk": "^0.1.20", "css-spring": "^4.1.0", "dayjs": "^1.11.7", - "lit": "^2.3.1", + "lit": "^2.6.1", "next": "13.1.0", "next-debug-local": "^0.1.5", "prettier": "^2.7.1", @@ -36,9 +36,10 @@ "quill-cursors": "^4.0.0", "react": "18.2.0", "react-dom": "18.2.0", - "yjs": "^13.5.44" + "yjs": "^13.5.45" }, "devDependencies": { + "@emotion/babel-plugin": "^11.10.5", "@types/node": "18.7.18", "@types/react": "18.0.20", "@types/react-dom": "18.0.6", @@ -50,7 +51,7 @@ "eslint-plugin-prettier": "^4.2.1", "next-pwa": "^5.6.0", "raw-loader": "^4.0.2", - "typescript": "4.8.3" + "typescript": "^4.9.5" }, "eslintConfig": { "extends": [ diff --git a/packages/app/public/globals.css b/packages/app/public/globals.css index ca43c3fec8..e949cd04c0 100644 --- a/packages/app/public/globals.css +++ b/packages/app/public/globals.css @@ -144,12 +144,12 @@ button, select, keygen, legend { - color: var(--affine-text-color); background-color: unset; outline: 0; border: 0; - font-size: 18px; - line-height: 1.5; + font-size: var(--affine-font-base); + line-height: var(--affine-line-height); + color: var(--affine-text-color); font-family: var(--affine-font-family); } body { @@ -173,7 +173,7 @@ input { } input:-webkit-autofill { - -webkit-box-shadow: 0 0 0px 1000px white inset; + -webkit-box-shadow: 0 0 0 1000px white inset; } input[type='number'] { diff --git a/packages/app/src/components/404/index.tsx b/packages/app/src/components/404/index.tsx index 36f1b90a34..ffba2a08d2 100644 --- a/packages/app/src/components/404/index.tsx +++ b/packages/app/src/components/404/index.tsx @@ -7,7 +7,7 @@ export const NotfoundPage = () => { const router = useRouter(); return ( - + {t('404 - Page Not Found')}

diff --git a/packages/app/src/components/contact-modal/index.tsx b/packages/app/src/components/contact-modal/index.tsx index 6ec64fc144..d22b2ea6c4 100644 --- a/packages/app/src/components/contact-modal/index.tsx +++ b/packages/app/src/components/contact-modal/index.tsx @@ -91,8 +91,6 @@ export const ContactModal = ({ Alpha { onClose(); }} diff --git a/packages/app/src/components/create-workspace/index.tsx b/packages/app/src/components/create-workspace/index.tsx index 9a26b56863..2c374a9900 100644 --- a/packages/app/src/components/create-workspace/index.tsx +++ b/packages/app/src/components/create-workspace/index.tsx @@ -53,13 +53,12 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => { {t('New Workspace')} -

- Workspace is your virtual space to capture, create and plan as - just one person or together as a team. -

+

{t('Workspace description')}

{ setWorkspaceName(value); }} diff --git a/packages/app/src/components/delete-workspace/index.tsx b/packages/app/src/components/delete-workspace/index.tsx index 5cceb487a2..cead4b135e 100644 --- a/packages/app/src/components/delete-workspace/index.tsx +++ b/packages/app/src/components/delete-workspace/index.tsx @@ -29,8 +29,6 @@ export const DeleteModal = ({
{ onClose(); }} diff --git a/packages/app/src/components/edgeless-toolbar/index.tsx b/packages/app/src/components/edgeless-toolbar/index.tsx index 33784928b9..e261899860 100644 --- a/packages/app/src/components/edgeless-toolbar/index.tsx +++ b/packages/app/src/components/edgeless-toolbar/index.tsx @@ -14,8 +14,8 @@ import { UndoIcon, RedoIcon, } from './Icons'; +import { MuiSlide } from '@/ui/mui'; import { Tooltip } from '@/ui/tooltip'; -import Slide from '@mui/material/Slide'; import useCurrentPageMeta from '@/hooks/use-current-page-meta'; import { useAppState } from '@/providers/app-state-provider'; import useHistoryUpdated from '@/hooks/use-history-update'; @@ -127,7 +127,7 @@ export const EdgelessToolbar = () => { const { mode } = useCurrentPageMeta() || {}; return ( - { - + ); }; diff --git a/packages/app/src/components/editor/index.tsx b/packages/app/src/components/editor/index.tsx index 40aacbe0a6..eff0dc21aa 100644 --- a/packages/app/src/components/editor/index.tsx +++ b/packages/app/src/components/editor/index.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'; import type { Page, Workspace } from '@blocksuite/store'; import '@blocksuite/blocks'; import { EditorContainer } from '@blocksuite/editor'; -import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhill.md'; +import exampleMarkdown from '@/templates/Welcome-to-AFFiNE-Alpha-Downhills.md'; import { styled } from '@/styles'; const StyledEditorContainer = styled('div')(() => { @@ -39,22 +39,21 @@ export const Editor = ({ page, workspace, setEditor }: Props) => { const title = metaTitle ? metaTitle : isFirstPage - ? 'Welcome to AFFiNE Alpha "Downhill"' + ? 'Welcome to AFFiNE Alpha "Downhills"' : ''; workspace?.setPageMeta(page.id, { title }); - - const pageId = page.addBlock({ - flavour: 'affine:page', - title, - }); - page.addBlock({ flavour: 'affine:surface' }); - const frameId = page.addBlock({ flavour: 'affine:frame' }, pageId); - page.addBlock({ flavour: 'affine:frame' }, pageId); + const pageBlockId = page.addBlockByFlavour('affine:page', { title }); + page.addBlockByFlavour('affine:surface', {}, null); + // Add frame block inside page block + const frameId = page.addBlockByFlavour('affine:frame', {}, pageBlockId); + // Add paragraph block inside frame block // If this is a first page in workspace, init an introduction markdown if (isFirstPage) { - editor.clipboard.importMarkdown(exampleMarkdown, `${frameId}`); + editor.clipboard.importMarkdown(exampleMarkdown, frameId); workspace.setPageMeta(page.id, { title }); page.resetHistory(); + } else { + page.addBlockByFlavour('affine:paragraph', {}, frameId); } page.resetHistory(); } diff --git a/packages/app/src/components/enable-workspace/EnableWorkspaceModal.tsx b/packages/app/src/components/enable-workspace/EnableWorkspaceModal.tsx new file mode 100644 index 0000000000..95da09511e --- /dev/null +++ b/packages/app/src/components/enable-workspace/EnableWorkspaceModal.tsx @@ -0,0 +1,111 @@ +import { styled } from '@/styles'; +import { Modal, ModalWrapper } from '@/ui/modal'; +import { Button, IconButton } from '@/ui/button'; +import { useTranslation } from '@affine/i18n'; +import { useAppState } from '@/providers/app-state-provider'; +import { useState } from 'react'; +import router from 'next/router'; +import { toast } from '@/ui/toast'; +import { CloseIcon } from '@blocksuite/icons'; +interface EnableWorkspaceModalProps { + open: boolean; + onClose: () => void; +} + +export const EnableWorkspaceModal = ({ + open, + onClose, +}: EnableWorkspaceModalProps) => { + const { t } = useTranslation(); + const { user, dataCenter, login, currentWorkspace } = useAppState(); + const [loading, setLoading] = useState(false); + return ( + + +
+ { + onClose(); + }} + > + + +
+ + {t('Enable AFFiNE Cloud')}? + {t('Enable AFFiNE Cloud Description')} + {/* {t('Retain local cached data')} */} +
+ { + setLoading(true); + if (!user) { + await login(); + } + if (currentWorkspace) { + const workspace = await dataCenter.enableWorkspaceCloud( + currentWorkspace + ); + workspace && + router.push(`/workspace/${workspace.id}/setting`); + toast(t('Enabled success')); + } + }} + > + {user ? t('Enable') : t('Sign in and Enable')} + + { + onClose(); + }} + > + {t('Not now')} + +
+
+
+
+ ); +}; + +const Header = styled('div')({ + height: '44px', + display: 'flex', + flexDirection: 'row-reverse', + paddingRight: '10px', + paddingTop: '10px', +}); + +const Content = styled('div')({ + textAlign: 'center', +}); + +const ContentTitle = styled('h1')({ + fontSize: '20px', + lineHeight: '28px', + fontWeight: 600, + textAlign: 'center', +}); + +const StyleTips = styled('div')(() => { + return { + userSelect: 'none', + width: '400px', + margin: 'auto', + marginBottom: '32px', + marginTop: '12px', + }; +}); + +const StyleButton = styled(Button)(() => { + return { + width: '284px', + display: 'block', + margin: 'auto', + marginTop: '16px', + }; +}); diff --git a/packages/app/src/components/enable-workspace/index.tsx b/packages/app/src/components/enable-workspace/index.tsx index 3cc28b25d4..cd4d558ff8 100644 --- a/packages/app/src/components/enable-workspace/index.tsx +++ b/packages/app/src/components/enable-workspace/index.tsx @@ -1,24 +1,28 @@ -import { useWorkspaceHelper } from '@/hooks/use-workspace-helper'; import { Button } from '@/ui/button'; import { useTranslation } from '@affine/i18n'; import { useState } from 'react'; +import { EnableWorkspaceModal } from './EnableWorkspaceModal'; export const EnableWorkspaceButton = () => { const { t } = useTranslation(); - const { enableWorkspace } = useWorkspaceHelper(); - const [loading, setLoading] = useState(false); + const [enableModalOpen, setEnableModalOpen] = useState(false); return ( - + <> + + { + setEnableModalOpen(false); + }} + > + ); }; diff --git a/packages/app/src/components/header/Header.tsx b/packages/app/src/components/header/Header.tsx index 4fe79467a7..4f4f86afc7 100644 --- a/packages/app/src/components/header/Header.tsx +++ b/packages/app/src/components/header/Header.tsx @@ -6,7 +6,7 @@ import { StyledBrowserWarning, StyledCloseButton, } from './styles'; -import CloseIcon from '@mui/icons-material/Close'; +import { CloseIcon } from '@blocksuite/icons'; import { useWarningMessage, shouldShowWarning } from './utils'; import EditorOptionMenu from './header-right-items/EditorOptionMenu'; import TrashButtonGroup from './header-right-items/TrashButtonGroup'; diff --git a/packages/app/src/components/header/PageListHeader.tsx b/packages/app/src/components/header/PageListHeader.tsx index 99efff25de..976c86b50d 100644 --- a/packages/app/src/components/header/PageListHeader.tsx +++ b/packages/app/src/components/header/PageListHeader.tsx @@ -1,5 +1,6 @@ import { PropsWithChildren, ReactNode } from 'react'; import Header from './Header'; +import QuickSearchButton from './QuickSearchButton'; import { StyledPageListTittleWrapper } from './styles'; // import QuickSearchButton from './QuickSearchButton'; @@ -12,7 +13,7 @@ export const PageListHeader = ({ icon, children }: PageListHeaderProps) => { {icon} {children} - {/* */} +
); diff --git a/packages/app/src/components/header/header-right-items/SyncUser.tsx b/packages/app/src/components/header/header-right-items/SyncUser.tsx index 60af1b461a..0923de9893 100644 --- a/packages/app/src/components/header/header-right-items/SyncUser.tsx +++ b/packages/app/src/components/header/header-right-items/SyncUser.tsx @@ -1,15 +1,41 @@ -import { CloudUnsyncedIcon, CloudInsyncIcon } from '@blocksuite/icons'; +import { CloudUnsyncedIcon } from '@blocksuite/icons'; import { useModal } from '@/providers/GlobalModalProvider'; import { useAppState } from '@/providers/app-state-provider'; import { IconButton } from '@/ui/button'; +// Temporary solution to use this component, since the @blocksuite/icons has not been published yet +const DefaultSyncIcon = () => { + return ( + + + + + ); +}; export const SyncUser = () => { const { triggerLoginModal } = useModal(); const appState = useAppState(); return appState.user ? ( - + ) : ( { export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => { return { - fontSize: theme.font.sm, + fontSize: theme.font.base, color: theme.colors.textColor, '>svg': { fontSize: '20px', diff --git a/packages/app/src/components/help-island/index.tsx b/packages/app/src/components/help-island/index.tsx index d6aad0c891..9fb883481d 100644 --- a/packages/app/src/components/help-island/index.tsx +++ b/packages/app/src/components/help-island/index.tsx @@ -6,7 +6,7 @@ import { StyledTransformIcon, } from './style'; import { CloseIcon, ContactIcon, HelpIcon, KeyboardIcon } from './Icons'; -import Grow from '@mui/material/Grow'; +import { MuiGrow } from '@/ui/mui'; import { Tooltip } from '@/ui/tooltip'; import { useTranslation } from '@affine/i18n'; import { useModal } from '@/providers/GlobalModalProvider'; @@ -35,7 +35,7 @@ export const HelpIsland = ({ setShowContent(false); }} > - + {showList.includes('contact') && ( @@ -66,7 +66,7 @@ export const HelpIsland = ({ )} - +
{ )} {status === 'importing' && ( - { OOOOPS! Sorry forgot to remind you that we are working on the import function - + )} diff --git a/packages/app/src/components/login-modal/GoogleIcon.tsx b/packages/app/src/components/login-modal/GoogleIcon.tsx new file mode 100644 index 0000000000..f0454955cb --- /dev/null +++ b/packages/app/src/components/login-modal/GoogleIcon.tsx @@ -0,0 +1,28 @@ +export const GoogleIcon = () => { + return ( + + + + + + + ); +}; diff --git a/packages/app/src/components/login-modal/Icons.tsx b/packages/app/src/components/login-modal/Icons.tsx deleted file mode 100644 index 12356f60d3..0000000000 --- a/packages/app/src/components/login-modal/Icons.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { CloudUnsyncedIcon } from '@blocksuite/icons'; -import { styled } from '@/styles'; -import GoogleSvg from './google.svg'; - -export const GoogleIcon = () => { - return ( - - - Google - - - ); -}; - -const GoogleIconWrapper = styled('div')(({ theme }) => ({ - 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/LoginOptionButton.tsx b/packages/app/src/components/login-modal/LoginOptionButton.tsx deleted file mode 100644 index f104efc58b..0000000000 --- a/packages/app/src/components/login-modal/LoginOptionButton.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { styled } from '@/styles'; -import { Button } from '@/ui/button'; -import { GoogleIcon, StayLogOutIcon } from './Icons'; -import { useTranslation } from '@affine/i18n'; -export const GoogleLoginButton = () => { - const { t } = useTranslation(); - - return ( - - - - - - {t('Continue with Google')} - - - ); -}; - -export const StayLogOutButton = () => { - const { t } = useTranslation(); - return ( - - - - - - - {t('Stay logged out')} - {t('All changes are saved locally')} - - - - ); -}; - -const StyledGoogleButton = styled('div')(({ theme }) => { - return { - width: '284px', - height: '40px', - marginTop: '30px', - fontSize: '16px', - cursor: 'pointer', - borderRadius: '40px', - border: `1px solid ${theme.colors.iconColor}`, - overflow: 'hidden', - ':hover': { - border: `1px solid ${theme.colors.primaryColor}`, - }, - }; -}); - -const StyledStayLogOutButton = styled(Button)(() => { - return { - width: '361px', - height: '56px', - padding: '4px', - ':hover': { - borderColor: '#6880FF', - }, - }; -}); - -const ButtonWrapper = styled('div')({ - display: 'flex', - flexDirection: 'row', - width: '100%', -}); - -const IconWrapper = styled('div')({ - flex: '0 48px', - borderRadius: '5px', - overflow: 'hidden', - marginRight: '12px', - marginTop: '8px', -}); - -const TextWrapper = styled('div')({ - flex: 1, - textAlign: 'left', - height: '40px', - lineHeight: '40px', -}); - -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 deleted file mode 100644 index 23af746fed..0000000000 --- a/packages/app/src/components/login-modal/google.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/packages/app/src/components/login-modal/index.tsx b/packages/app/src/components/login-modal/index.tsx index d572190ee9..880ebe6e38 100644 --- a/packages/app/src/components/login-modal/index.tsx +++ b/packages/app/src/components/login-modal/index.tsx @@ -1,7 +1,9 @@ -import { styled } from '@/styles'; +import { positionAbsolute, styled } from '@/styles'; import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal'; -import { GoogleLoginButton } from './LoginOptionButton'; +import { Button } from '@/ui/button'; import { useAppState } from '@/providers/app-state-provider'; +import { useTranslation } from '@affine/i18n'; +import { GoogleIcon } from './GoogleIcon'; interface LoginModalProps { open: boolean; onClose: () => void; @@ -9,38 +11,44 @@ interface LoginModalProps { export const LoginModal = ({ open, onClose }: LoginModalProps) => { const { login } = useAppState(); + const { t } = useTranslation(); return ( - -
- { - onClose(); - }} - /> -
+ + { + onClose(); + }} + /> - {'Sign in'} - Set up an AFFINE account to sync data - {t('Sign in')} + {t('Set up an AFFiNE account to sync data')} + { await login(); onClose(); }} > - - + + {t('Continue with Google')} +
); }; -const Header = styled('div')({ - position: 'relative', - height: '44px', +const StyledLoginButton = styled(Button)(() => { + return { + width: '284px', + marginTop: '30px', + position: 'relative', + svg: { + ...positionAbsolute({ left: '18px', top: '0', bottom: '0' }), + margin: 'auto', + }, + }; }); const Content = styled('div')({ diff --git a/packages/app/src/components/logout-modal/icon.tsx b/packages/app/src/components/logout-modal/icon.tsx index df23248b78..afca63f57f 100644 --- a/packages/app/src/components/logout-modal/icon.tsx +++ b/packages/app/src/components/logout-modal/icon.tsx @@ -8,7 +8,7 @@ export const Check = () => { fill="none" xmlns="http://www.w3.org/2000/svg" > - + void; } export const LogoutModal = ({ open, onClose }: LoginModalProps) => { - const [localCache, setLocalCache] = useState(false); + const [localCache, setLocalCache] = useState(true); + const { blobDataSynced } = useAppState(); + const { t } = useTranslation(); return (
{ onClose(true); }} />
- {'Sign out?'} - Set up an AFFINE account to sync data + {t('Sign out')}? + + {blobDataSynced + ? t('Set up an AFFiNE account to sync data') + : 'All data has been stored in the cloud'} + {localCache ? ( { )} - Retain local cached data + {t('Retain local cached data')} -
- - -
+ {blobDataSynced ? ( +
+ + +
+ ) : ( +
+ + +
+ )}
diff --git a/packages/app/src/components/page-list/Empty.tsx b/packages/app/src/components/page-list/Empty.tsx index 4430b8f1a9..f8dfc6745d 100644 --- a/packages/app/src/components/page-list/Empty.tsx +++ b/packages/app/src/components/page-list/Empty.tsx @@ -14,7 +14,6 @@ export const PageListEmpty = (props: { listType?: string }) => { {listType === 'all' &&

{t('emptyAllPages')}

} {listType === 'favorite' &&

{t('emptyFavourite')}

} {listType === 'trash' &&

{t('emptyTrash')}

} -

{t('still designed')}

); }; diff --git a/packages/app/src/components/page-list/OperationCell.tsx b/packages/app/src/components/page-list/OperationCell.tsx index ca59efa60a..a9efc634c4 100644 --- a/packages/app/src/components/page-list/OperationCell.tsx +++ b/packages/app/src/components/page-list/OperationCell.tsx @@ -1,7 +1,7 @@ import { useConfirm } from '@/providers/ConfirmProvider'; import { PageMeta } from '@/providers/app-state-provider'; import { Menu, MenuItem } from '@/ui/menu'; -import { Wrapper } from '@/ui/layout'; +import { FlexWrapper } from '@/ui/layout'; import { IconButton } from '@/ui/button'; import { MoreVerticalIcon, @@ -63,13 +63,13 @@ export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { ); return ( - + - + ); }; @@ -80,7 +80,7 @@ export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { const { confirm } = useConfirm(); const { t } = useTranslation(); return ( - + { > - + ); }; diff --git a/packages/app/src/components/quick-search/Footer.tsx b/packages/app/src/components/quick-search/Footer.tsx index 14b37561b6..cc84003a5b 100644 --- a/packages/app/src/components/quick-search/Footer.tsx +++ b/packages/app/src/components/quick-search/Footer.tsx @@ -1,26 +1,23 @@ import React from 'react'; import { AddIcon } from '@blocksuite/icons'; import { StyledModalFooterContent } from './style'; -import { useModal } from '@/providers/GlobalModalProvider'; import { Command } from 'cmdk'; import { usePageHelper } from '@/hooks/use-page-helper'; import { useTranslation } from '@affine/i18n'; -export const Footer = (props: { query: string }) => { - const { triggerQuickSearchModal } = useModal(); +export const Footer = (props: { query: string; onClose: () => void }) => { const { openPage, createPage } = usePageHelper(); const { t } = useTranslation(); - const query = props.query; + const { query, onClose } = props; return ( { + onClose(); const pageId = await createPage({ title: query }); if (pageId) { openPage(pageId); } - - triggerQuickSearchModal(); }} > diff --git a/packages/app/src/components/quick-search/Input.tsx b/packages/app/src/components/quick-search/Input.tsx index 6d96d239b0..0ac61c54c2 100644 --- a/packages/app/src/components/quick-search/Input.tsx +++ b/packages/app/src/components/quick-search/Input.tsx @@ -8,32 +8,41 @@ import React, { import { SearchIcon } from '@blocksuite/icons'; import { StyledInputContent, StyledLabel } from './style'; import { Command } from 'cmdk'; -import { useAppState } from '@/providers/app-state-provider'; import { useTranslation } from '@affine/i18n'; export const Input = (props: { + open: boolean; query: string; setQuery: Dispatch>; setLoading: Dispatch>; + isPublic: boolean; + publishWorkspaceName: string | undefined; }) => { + const { open, query, setQuery, setLoading, isPublic, publishWorkspaceName } = + props; const [isComposition, setIsComposition] = useState(false); const [inputValue, setInputValue] = useState(''); const inputRef = useRef(null); - const { currentWorkspace } = useAppState(); const { t } = useTranslation(); useEffect(() => { - inputRef.current?.addEventListener( - 'blur', - () => { - inputRef.current?.focus(); - }, - true - ); - return inputRef.current?.focus(); - }, [inputRef]); + if (open) { + const inputElement = inputRef.current; + return inputElement?.focus(); + } + }, [open]); useEffect(() => { - return setInputValue(props.query); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + const inputElement = inputRef.current; + if (!open) { + return; + } + const handleFocus = () => { + inputElement?.focus(); + }; + inputElement?.addEventListener('blur', handleFocus, true); + return () => inputElement?.removeEventListener('blur', handleFocus, true); + }, [inputRef, open]); + useEffect(() => { + setInputValue(query); + }, [query]); return ( @@ -46,18 +55,18 @@ export const Input = (props: { setIsComposition(true); }} onCompositionEnd={e => { - props.setQuery(e.data); + setQuery(e.data); setIsComposition(false); - if (!props.query) { - props.setLoading(true); + if (!query) { + setLoading(true); } }} onValueChange={str => { setInputValue(str); if (!isComposition) { - props.setQuery(str); - if (!props.query) { - props.setLoading(true); + setQuery(str); + if (!query) { + setLoading(true); } } }} @@ -78,9 +87,9 @@ export const Input = (props: { } }} placeholder={ - currentWorkspace?.isPublish + isPublic ? t('Quick search placeholder2', { - workspace: currentWorkspace?.blocksuiteWorkspace?.meta.name, + workspace: publishWorkspaceName, }) : t('Quick search placeholder') } diff --git a/packages/app/src/components/quick-search/PublishedResults.tsx b/packages/app/src/components/quick-search/PublishedResults.tsx new file mode 100644 index 0000000000..d0564b4cd7 --- /dev/null +++ b/packages/app/src/components/quick-search/PublishedResults.tsx @@ -0,0 +1,95 @@ +import { Command } from 'cmdk'; +import { StyledListItem, StyledNotFound } from './style'; +import { PaperIcon, EdgelessIcon } from '@blocksuite/icons'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; +import { useAppState, PageMeta } from '@/providers/app-state-provider'; +import { useRouter } from 'next/router'; +import { NoResultSVG } from './NoResultSVG'; +import { useTranslation } from '@affine/i18n'; +import usePageHelper from '@/hooks/use-page-helper'; +import { Workspace } from '@blocksuite/store'; + +export const PublishedResults = (props: { + query: string; + loading: boolean; + setLoading: Dispatch>; + setPublishWorkspaceName: Dispatch>; + onClose: () => void; +}) => { + const [workspace, setWorkspace] = useState(); + const { query, loading, setLoading, onClose, setPublishWorkspaceName } = + props; + const { search } = usePageHelper(); + const [results, setResults] = useState(new Map()); + const { dataCenter } = useAppState(); + const router = useRouter(); + const [pageList, setPageList] = useState([]); + useEffect(() => { + dataCenter + .loadPublicWorkspace(router.query.workspaceId as string) + .then(data => { + setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]); + if (data.blocksuiteWorkspace) { + setWorkspace(data.blocksuiteWorkspace); + setPublishWorkspaceName(data.blocksuiteWorkspace.meta.name); + } + }) + .catch(() => { + router.push('/404'); + }); + }, [router, dataCenter, setPublishWorkspaceName]); + const { t } = useTranslation(); + useEffect(() => { + setResults(search(query, workspace)); + 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 = pageList.filter( + page => pageIds.indexOf(page.id) > -1 && !page.trash + ); + + return loading ? null : ( + <> + {query ? ( + resultsPageMeta.length ? ( + + {resultsPageMeta.map(result => { + return ( + { + router.push( + `/public-workspace/${router.query.workspaceId}/${result.id}` + ); + onClose(); + }} + value={result.id} + > + + {result.mode === 'edgeless' ? ( + + ) : ( + + )} + {result.title} + + + ); + })} + + ) : ( + + {t('Find 0 result')} + + + ) + ) : ( + <> + )} + + ); +}; diff --git a/packages/app/src/components/quick-search/Results.tsx b/packages/app/src/components/quick-search/Results.tsx index 9e23c5f7c6..d7c8eddb0b 100644 --- a/packages/app/src/components/quick-search/Results.tsx +++ b/packages/app/src/components/quick-search/Results.tsx @@ -1,6 +1,5 @@ import { Command } from 'cmdk'; import { StyledListItem, StyledNotFound } from './style'; -import { useModal } from '@/providers/GlobalModalProvider'; import { PaperIcon, EdgelessIcon } from '@blocksuite/icons'; import { Dispatch, SetStateAction, useEffect, useState } from 'react'; import { useAppState } from '@/providers/app-state-provider'; @@ -12,14 +11,11 @@ import usePageHelper from '@/hooks/use-page-helper'; export const Results = (props: { query: string; loading: boolean; + onClose: () => void; setLoading: Dispatch>; setShowCreatePage: Dispatch>; }) => { - const query = props.query; - const loading = props.loading; - const setLoading = props.setLoading; - const setShowCreatePage = props.setShowCreatePage; - const { triggerQuickSearchModal } = useModal(); + const { query, loading, setLoading, setShowCreatePage, onClose } = props; const { openPage } = usePageHelper(); const router = useRouter(); const { currentWorkspace, pageList } = useAppState(); @@ -55,8 +51,8 @@ export const Results = (props: { { + onClose(); openPage(result.id); - triggerQuickSearchModal(); }} value={result.id} > @@ -86,8 +82,8 @@ export const Results = (props: { key={link.title} value={link.title} onSelect={() => { + onClose(); router.push(link.href); - triggerQuickSearchModal(); }} > diff --git a/packages/app/src/components/quick-search/config.ts b/packages/app/src/components/quick-search/config.ts index 4738e93554..9aa284558e 100644 --- a/packages/app/src/components/quick-search/config.ts +++ b/packages/app/src/components/quick-search/config.ts @@ -1,5 +1,10 @@ import { FC, SVGProps } from 'react'; -import { AllPagesIcon, FavouritesIcon, TrashIcon } from '@blocksuite/icons'; +import { + AllPagesIcon, + FavouritesIcon, + TrashIcon, + SettingsIcon, +} from '@blocksuite/icons'; import { useTranslation } from '@affine/i18n'; export const useSwitchToConfig = ( @@ -23,6 +28,13 @@ export const useSwitchToConfig = ( : '', icon: FavouritesIcon, }, + { + title: t('Settings'), + href: currentWorkspaceId + ? `/workspace/${currentWorkspaceId}/setting` + : '', + icon: SettingsIcon, + }, { title: t('Trash'), href: currentWorkspaceId ? `/workspace/${currentWorkspaceId}/trash` : '', diff --git a/packages/app/src/components/quick-search/index.tsx b/packages/app/src/components/quick-search/index.tsx index 9d6e477968..97921efca7 100644 --- a/packages/app/src/components/quick-search/index.tsx +++ b/packages/app/src/components/quick-search/index.tsx @@ -13,7 +13,8 @@ import { Command } from 'cmdk'; import { useEffect, useState } from 'react'; import { useModal } from '@/providers/GlobalModalProvider'; import { getUaHelper } from '@/utils'; -import { useAppState } from '@/providers/app-state-provider'; +import { useRouter } from 'next/router'; +import { PublishedResults } from './PublishedResults'; type TransitionsModalProps = { open: boolean; onClose: () => void; @@ -22,18 +23,30 @@ const isMac = () => { return getUaHelper().isMacOs; }; export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { - const { currentWorkspace } = useAppState(); - + const router = useRouter(); const [query, setQuery] = useState(''); const [loading, setLoading] = useState(true); + const [isPublic, setIsPublic] = useState(false); + const [publishWorkspaceName, setPublishWorkspaceName] = useState(''); const [showCreatePage, setShowCreatePage] = useState(true); const { triggerQuickSearchModal } = useModal(); - + const isPublicAndNoQuery = () => { + return isPublic && query.length === 0; + }; + const handleClose = () => { + setQuery(''); + onClose(); + }; // Add ‘⌘+K’ shortcut keys as switches useEffect(() => { + if (router.pathname.startsWith('/404')) { + triggerQuickSearchModal(false); + return; + } const down = (e: KeyboardEvent) => { if ((e.key === 'k' && e.metaKey) || (e.key === 'k' && e.ctrlKey)) { const selection = window.getSelection(); + setQuery(''); if (selection?.toString()) { triggerQuickSearchModal(false); return; @@ -43,18 +56,23 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { } } }; - if (!open) { - setQuery(''); - } document.addEventListener('keydown', down, { capture: true }); return () => document.removeEventListener('keydown', down, { capture: true }); - }, [open, triggerQuickSearchModal]); + }, [open, router.pathname, triggerQuickSearchModal]); + + useEffect(() => { + if (router.pathname.startsWith('/public-workspace')) { + return setIsPublic(true); + } else { + return setIsPublic(false); + } + }, [router]); return ( @@ -62,7 +80,7 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { width={620} style={{ maxHeight: '80vh', - minHeight: '350px', + minHeight: isPublicAndNoQuery() ? '72px' : '350px', top: '12vh', }} > @@ -81,28 +99,51 @@ export const QuickSearch = ({ open, onClose }: TransitionsModalProps) => { }} > - + {isMac() ? '⌘ + K' : 'Ctrl + K'} - + - - + + {!isPublic ? ( + + ) : ( + + )} - {currentWorkspace?.published ? ( - <> - ) : showCreatePage ? ( - <> - - -