mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 00:56:26 +08:00
Merge branch 'master'
Conflicts: .vscode/settings.json package.json packages/data-center/package.json pnpm-lock.yaml
This commit is contained in:
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
**/webpack.config.js
|
||||
**/scripts/*.js
|
||||
**/node_modules/**
|
||||
.github/**
|
||||
**/__tests__/**
|
||||
**/tests/**
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<!--
|
||||
For bug reports 🐛, please provide:
|
||||
- The necessary steps to reproduce the issue.
|
||||
- The browser and platform you tested, if this may be related to compatibility.
|
||||
|
||||
For general discussion and feature requests, please use 👉 https://community.affine.pro/
|
||||
-->
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
name: I have a question
|
||||
about: Feel free to ask us your questions!
|
||||
title: '[Question]'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
---
|
||||
+35
-19
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -4,6 +4,8 @@
|
||||
*dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
.nyc_output
|
||||
.coverage
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
Vendored
+3
-1
@@ -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"
|
||||
|
||||
@@ -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 <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||
|
||||
For answers to common questions about this code of conduct, see <https://www.contributor-covenant.org/faq>
|
||||
@@ -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.
|
||||
@@ -18,13 +18,14 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
-->
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
||||
|
||||
[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
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||
|
||||
[](https://livedemo.affine.pro)
|
||||
[](https://github.com/toeverything/AFFiNE)
|
||||
[![All Contributors][all-contributors-badge]](#contributors)
|
||||

|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://reactjs.org/)
|
||||
@@ -80,9 +81,9 @@ Before we tell you how to get started with AFFiNE, we'd like to shamelessly plug
|
||||
|
||||
[](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,
|
||||
<td align="center"><a href="https://github.com/MuhammedFaraz"><img src="https://avatars.githubusercontent.com/u/92734739?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Mohammed Faraz</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=MuhammedFaraz" title="Documentation">📖</a></td>
|
||||
<td align="center"><a href="https://pranavsriram.dev/"><img src="https://avatars.githubusercontent.com/u/28348429?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Pranav Sriram </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Pranav4399" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/Reson-a"><img src="https://avatars.githubusercontent.com/u/20806266?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Reson-a</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Reson-a" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://t.me/littlepoint"><img src="https://avatars.githubusercontent.com/u/7611700?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Zhizhen He</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=hezhizhen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://akr.moe/"><img src="https://avatars.githubusercontent.com/u/85140972?v=4?s=50" width="50px;" alt=""/><br /><sub><b>AkaraChen</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=AkaraChen" title="Code">💻</a></td>
|
||||
<td align="center"><a href="https://github.com/suyanhanx"><img src="https://avatars.githubusercontent.com/u/24221472?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Suyan</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=suyanhanx" title="Code">💻</a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -192,10 +195,26 @@ Thanks a lot to the community for providing such powerful and simple libraries,
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
## 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.
|
||||
|
||||
+10
-10
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 <https://github.com/toeverything/blocksuite/compare/4e0463b...b22bea7>.
|
||||
|
||||
## Fix the breaking change
|
||||
|
||||
You can follow this file to keep updated:
|
||||
|
||||
https://github.com/toeverything/blocksuite/blob/master/packages/playground/src/main.ts
|
||||
@@ -4,6 +4,7 @@
|
||||
- https://allcontributors.org/docs/en/emoji-key
|
||||
|
||||
```shell
|
||||
all-contributors check
|
||||
all-contributors add tzhangchi code,doc
|
||||
all-contributors generate
|
||||
```
|
||||
|
||||
@@ -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).
|
||||
|
||||
+13
-8
@@ -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/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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',
|
||||
});
|
||||
|
||||
|
||||
@@ -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": [
|
||||
|
||||
@@ -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'] {
|
||||
|
||||
@@ -7,7 +7,7 @@ export const NotfoundPage = () => {
|
||||
const router = useRouter();
|
||||
return (
|
||||
<PageContainer>
|
||||
<NotFoundTitle>
|
||||
<NotFoundTitle data-testid="notFound">
|
||||
{t('404 - Page Not Found')}
|
||||
<p>
|
||||
<Button
|
||||
@@ -15,7 +15,7 @@ export const NotfoundPage = () => {
|
||||
router.push('/workspace');
|
||||
}}
|
||||
>
|
||||
Back Home
|
||||
{t('Back Home')}
|
||||
</Button>
|
||||
</p>
|
||||
</NotFoundTitle>
|
||||
|
||||
@@ -91,8 +91,6 @@ export const ContactModal = ({
|
||||
<span>Alpha</span>
|
||||
</StyledModalHeaderLeft>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
||||
@@ -53,13 +53,12 @@ export const CreateWorkspaceModal = ({ open, onClose }: ModalProps) => {
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('New Workspace')}</ContentTitle>
|
||||
<p>
|
||||
Workspace is your virtual space to capture, create and plan as
|
||||
just one person or together as a team.
|
||||
</p>
|
||||
<p>{t('Workspace description')}</p>
|
||||
<Input
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={'Set a Workspace name'}
|
||||
placeholder={t('Set a Workspace name')}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
onChange={value => {
|
||||
setWorkspaceName(value);
|
||||
}}
|
||||
|
||||
@@ -29,8 +29,6 @@ export const DeleteModal = ({
|
||||
<ModalWrapper width={620} height={334}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
|
||||
@@ -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 (
|
||||
<Slide
|
||||
<MuiSlide
|
||||
direction="right"
|
||||
in={mode === 'edgeless'}
|
||||
mountOnEnter
|
||||
@@ -155,7 +155,7 @@ export const EdgelessToolbar = () => {
|
||||
</StyledToolbarWrapper>
|
||||
<UndoRedo />
|
||||
</StyledEdgelessToolbar>
|
||||
</Slide>
|
||||
</MuiSlide>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{t('Enable AFFiNE Cloud')}?</ContentTitle>
|
||||
<StyleTips>{t('Enable AFFiNE Cloud Description')}</StyleTips>
|
||||
{/* <StyleTips>{t('Retain local cached data')}</StyleTips> */}
|
||||
<div>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
type="primary"
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
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')}
|
||||
</StyleButton>
|
||||
<StyleButton
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t('Not now')}
|
||||
</StyleButton>
|
||||
</div>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
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',
|
||||
};
|
||||
});
|
||||
@@ -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 (
|
||||
<Button
|
||||
type="primary"
|
||||
shape="circle"
|
||||
loading={loading}
|
||||
onClick={async () => {
|
||||
setLoading(true);
|
||||
await enableWorkspace();
|
||||
setLoading(false);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
type="light"
|
||||
shape="circle"
|
||||
onClick={async () => {
|
||||
setEnableModalOpen(true);
|
||||
}}
|
||||
>
|
||||
{t('Enable AFFiNE Cloud')}
|
||||
</Button>
|
||||
<EnableWorkspaceModal
|
||||
open={enableModalOpen}
|
||||
onClose={() => {
|
||||
setEnableModalOpen(false);
|
||||
}}
|
||||
></EnableWorkspaceModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) => {
|
||||
<StyledPageListTittleWrapper>
|
||||
{icon}
|
||||
{children}
|
||||
{/* <QuickSearchButton style={{ marginLeft: '5px' }} /> */}
|
||||
<QuickSearchButton />
|
||||
</StyledPageListTittleWrapper>
|
||||
</Header>
|
||||
);
|
||||
|
||||
@@ -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 (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M3 13.6493C3 16.6044 5.41766 19 8.4 19L16.5 19C18.9853 19 21 16.9839 21 14.4969C21 12.6503 19.8893 10.9449 18.3 10.25C18.1317 7.32251 15.684 5 12.6893 5C10.3514 5 8.34694 6.48637 7.5 8.5C4.8 8.9375 3 11.2001 3 13.6493Z"
|
||||
stroke="#888A9E"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M11.4571 9L9 16H10.4392L12.0021 11.1586L13.5657 16H15L12.5425 9H11.4571Z"
|
||||
fill="#888A9E"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
export const SyncUser = () => {
|
||||
const { triggerLoginModal } = useModal();
|
||||
const appState = useAppState();
|
||||
|
||||
return appState.user ? (
|
||||
<IconButton iconSize="middle" disabled>
|
||||
<CloudInsyncIcon />
|
||||
<DefaultSyncIcon />
|
||||
</IconButton>
|
||||
) : (
|
||||
<IconButton
|
||||
|
||||
@@ -103,7 +103,7 @@ export const StyledSearchArrowWrapper = styled.div(() => {
|
||||
|
||||
export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.sm,
|
||||
fontSize: theme.font.base,
|
||||
color: theme.colors.textColor,
|
||||
'>svg': {
|
||||
fontSize: '20px',
|
||||
|
||||
@@ -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);
|
||||
}}
|
||||
>
|
||||
<Grow in={showContent}>
|
||||
<MuiGrow in={showContent}>
|
||||
<StyledIslandWrapper>
|
||||
{showList.includes('contact') && (
|
||||
<Tooltip content={t('Contact Us')} placement="left-end">
|
||||
@@ -66,7 +66,7 @@ export const HelpIsland = ({
|
||||
</Tooltip>
|
||||
)}
|
||||
</StyledIslandWrapper>
|
||||
</Grow>
|
||||
</MuiGrow>
|
||||
|
||||
<div style={{ position: 'relative' }}>
|
||||
<StyledIconWrapper
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { StyledButtonWrapper, StyledTitle } from './styles';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Wrapper, Content } from '@/ui/layout';
|
||||
import { Content, FlexWrapper } from '@/ui/layout';
|
||||
import Loading from '@/components/loading';
|
||||
import { usePageHelper } from '@/hooks/use-page-helper';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -116,7 +116,7 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
|
||||
)}
|
||||
|
||||
{status === 'importing' && (
|
||||
<Wrapper
|
||||
<FlexWrapper
|
||||
wrap={true}
|
||||
justifyContent="center"
|
||||
style={{ marginTop: 22, paddingBottom: '32px' }}
|
||||
@@ -126,7 +126,7 @@ export const ImportModal = ({ open, onClose }: ImportModalProps) => {
|
||||
OOOOPS! Sorry forgot to remind you that we are working on the
|
||||
import function
|
||||
</Content>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
)}
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
export const GoogleIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="25"
|
||||
height="24"
|
||||
viewBox="0 0 25 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.327 16.3285 15.1115 18 12.5 18C9.1865 18 6.5 15.3135 6.5 12C6.5 8.6865 9.1865 6 12.5 6C14.0295 6 15.421 6.577 16.4805 7.5195L19.309 4.691C17.523 3.0265 15.134 2 12.5 2C6.9775 2 2.5 6.4775 2.5 12C2.5 17.5225 6.9775 22 12.5 22C18.0225 22 22.5 17.5225 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z"
|
||||
fill="#FFC107"
|
||||
/>
|
||||
<path
|
||||
d="M3.65234 7.3455L6.93784 9.755C7.82684 7.554 9.97984 6 12.4993 6C14.0288 6 15.4203 6.577 16.4798 7.5195L19.3083 4.691C17.5223 3.0265 15.1333 2 12.4993 2C8.65834 2 5.32734 4.1685 3.65234 7.3455Z"
|
||||
fill="#FF3D00"
|
||||
/>
|
||||
<path
|
||||
d="M12.5002 22.0003C15.0832 22.0003 17.4302 21.0118 19.2047 19.4043L16.1097 16.7853C15.0719 17.5745 13.8039 18.0014 12.5002 18.0003C9.89916 18.0003 7.69066 16.3418 6.85866 14.0273L3.59766 16.5398C5.25266 19.7783 8.61366 22.0003 12.5002 22.0003Z"
|
||||
fill="#4CAF50"
|
||||
/>
|
||||
<path
|
||||
d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.7571 15.1082 17.0467 16.0766 16.108 16.7855L16.1095 16.7845L19.2045 19.4035C18.9855 19.6025 22.5 17 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z"
|
||||
fill="#1976D2"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { CloudUnsyncedIcon } from '@blocksuite/icons';
|
||||
import { styled } from '@/styles';
|
||||
import GoogleSvg from './google.svg';
|
||||
|
||||
export const GoogleIcon = () => {
|
||||
return (
|
||||
<GoogleIconWrapper>
|
||||
<picture>
|
||||
<img src={GoogleSvg.src} alt="Google" />
|
||||
</picture>
|
||||
</GoogleIconWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const GoogleIconWrapper = styled('div')(({ theme }) => ({
|
||||
background: theme.colors.pageBackground,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}));
|
||||
|
||||
export const StayLogOutIcon = () => {
|
||||
return (
|
||||
<StayLogOutWrapper>
|
||||
<CloudUnsyncedIcon />
|
||||
</StayLogOutWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const StayLogOutWrapper = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '48px',
|
||||
height: '48px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize: '24px',
|
||||
background: theme.colors.hoverBackground,
|
||||
};
|
||||
});
|
||||
@@ -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 (
|
||||
<StyledGoogleButton>
|
||||
<ButtonWrapper>
|
||||
<IconWrapper>
|
||||
<GoogleIcon />
|
||||
</IconWrapper>
|
||||
<TextWrapper>{t('Continue with Google')}</TextWrapper>
|
||||
</ButtonWrapper>
|
||||
</StyledGoogleButton>
|
||||
);
|
||||
};
|
||||
|
||||
export const StayLogOutButton = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledStayLogOutButton>
|
||||
<ButtonWrapper>
|
||||
<IconWrapper>
|
||||
<StayLogOutIcon />
|
||||
</IconWrapper>
|
||||
<TextWrapper>
|
||||
<Title>{t('Stay logged out')}</Title>
|
||||
<Description>{t('All changes are saved locally')}</Description>
|
||||
</TextWrapper>
|
||||
</ButtonWrapper>
|
||||
</StyledStayLogOutButton>
|
||||
);
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.327 16.3285 15.1115 18 12.5 18C9.1865 18 6.5 15.3135 6.5 12C6.5 8.6865 9.1865 6 12.5 6C14.0295 6 15.421 6.577 16.4805 7.5195L19.309 4.691C17.523 3.0265 15.134 2 12.5 2C6.9775 2 2.5 6.4775 2.5 12C2.5 17.5225 6.9775 22 12.5 22C18.0225 22 22.5 17.5225 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#FFC107"/>
|
||||
<path d="M3.65234 7.3455L6.93784 9.755C7.82684 7.554 9.97984 6 12.4993 6C14.0288 6 15.4203 6.577 16.4798 7.5195L19.3083 4.691C17.5223 3.0265 15.1333 2 12.4993 2C8.65834 2 5.32734 4.1685 3.65234 7.3455Z" fill="#FF3D00"/>
|
||||
<path d="M12.5002 22.0003C15.0832 22.0003 17.4302 21.0118 19.2047 19.4043L16.1097 16.7853C15.0719 17.5745 13.8039 18.0014 12.5002 18.0003C9.89916 18.0003 7.69066 16.3418 6.85866 14.0273L3.59766 16.5398C5.25266 19.7783 8.61366 22.0003 12.5002 22.0003Z" fill="#4CAF50"/>
|
||||
<path d="M22.3055 10.0415H21.5V10H12.5V14H18.1515C17.7571 15.1082 17.0467 16.0766 16.108 16.7855L16.1095 16.7845L19.2045 19.4035C18.9855 19.6025 22.5 17 22.5 12C22.5 11.3295 22.431 10.675 22.3055 10.0415Z" fill="#1976D2"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@@ -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 (
|
||||
<Modal open={open} onClose={onClose} data-testid="login-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<ModalWrapper width={560} height={292} style={{ paddingTop: '44px' }}>
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
/>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign in'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<span
|
||||
<ContentTitle>{t('Sign in')}</ContentTitle>
|
||||
<SignDes>{t('Set up an AFFiNE account to sync data')}</SignDes>
|
||||
<StyledLoginButton
|
||||
shape="round"
|
||||
onClick={async () => {
|
||||
await login();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<GoogleLoginButton />
|
||||
</span>
|
||||
<GoogleIcon />
|
||||
{t('Continue with Google')}
|
||||
</StyledLoginButton>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
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')({
|
||||
|
||||
@@ -8,7 +8,7 @@ export const Check = () => {
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clip-path="url(#clip0_9266_16831)">
|
||||
<g clipPath="url(#clip0_9266_16831)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
|
||||
@@ -3,28 +3,34 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Check, UnCheck } from './icon';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
interface LoginModalProps {
|
||||
open: boolean;
|
||||
onClose: (wait: boolean) => void;
|
||||
}
|
||||
|
||||
export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
const [localCache, setLocalCache] = useState(false);
|
||||
const [localCache, setLocalCache] = useState(true);
|
||||
const { blobDataSynced } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Modal open={open} onClose={onClose} data-testid="logout-modal">
|
||||
<ModalWrapper width={560} height={292}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
<Content>
|
||||
<ContentTitle>{'Sign out?'}</ContentTitle>
|
||||
<SignDes>Set up an AFFINE account to sync data</SignDes>
|
||||
<ContentTitle>{t('Sign out')}?</ContentTitle>
|
||||
<SignDes>
|
||||
{blobDataSynced
|
||||
? t('Set up an AFFiNE account to sync data')
|
||||
: 'All data has been stored in the cloud'}
|
||||
</SignDes>
|
||||
<StyleTips>
|
||||
{localCache ? (
|
||||
<StyleCheck
|
||||
@@ -43,28 +49,51 @@ export const LogoutModal = ({ open, onClose }: LoginModalProps) => {
|
||||
<UnCheck></UnCheck>
|
||||
</StyleCheck>
|
||||
)}
|
||||
Retain local cached data
|
||||
{t('Retain local cached data')}
|
||||
</StyleTips>
|
||||
<div>
|
||||
<Button
|
||||
style={{ marginRight: '16px' }}
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
Wait for Sync
|
||||
</Button>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
Force Sign Out
|
||||
</Button>
|
||||
</div>
|
||||
{blobDataSynced ? (
|
||||
<div>
|
||||
<Button
|
||||
type="danger"
|
||||
shape="round"
|
||||
style={{ marginRight: '16px' }}
|
||||
onClick={async () => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
{t('Force Sign Out')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
{t('Wait for Sync')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Button
|
||||
type="primary"
|
||||
style={{ marginRight: '16px' }}
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(true);
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
shape="round"
|
||||
onClick={() => {
|
||||
onClose(false);
|
||||
}}
|
||||
>
|
||||
{t('Sign out')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
|
||||
@@ -14,7 +14,6 @@ export const PageListEmpty = (props: { listType?: string }) => {
|
||||
{listType === 'all' && <p>{t('emptyAllPages')}</p>}
|
||||
{listType === 'favorite' && <p>{t('emptyFavourite')}</p>}
|
||||
{listType === 'trash' && <p>{t('emptyTrash')}</p>}
|
||||
<p>{t('still designed')}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<Wrapper alignItems="center" justifyContent="center">
|
||||
<FlexWrapper alignItems="center" justifyContent="center">
|
||||
<Menu content={OperationMenu} placement="bottom-end" disablePortal={true}>
|
||||
<IconButton darker={true}>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -80,7 +80,7 @@ export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||
const { confirm } = useConfirm();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Wrapper>
|
||||
<FlexWrapper>
|
||||
<IconButton
|
||||
darker={true}
|
||||
style={{ marginRight: '12px' }}
|
||||
@@ -108,6 +108,6 @@ export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => {
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
<Command.Item
|
||||
data-testid="quickSearch-addNewPage"
|
||||
onSelect={async () => {
|
||||
onClose();
|
||||
const pageId = await createPage({ title: query });
|
||||
if (pageId) {
|
||||
openPage(pageId);
|
||||
}
|
||||
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<StyledModalFooterContent>
|
||||
|
||||
@@ -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<SetStateAction<string>>;
|
||||
setLoading: Dispatch<SetStateAction<boolean>>;
|
||||
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<HTMLInputElement>(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 (
|
||||
<StyledInputContent>
|
||||
<StyledLabel htmlFor=":r5:">
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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<SetStateAction<boolean>>;
|
||||
setPublishWorkspaceName: Dispatch<SetStateAction<string>>;
|
||||
onClose: () => void;
|
||||
}) => {
|
||||
const [workspace, setWorkspace] = useState<Workspace>();
|
||||
const { query, loading, setLoading, onClose, setPublishWorkspaceName } =
|
||||
props;
|
||||
const { search } = usePageHelper();
|
||||
const [results, setResults] = useState(new Map<string, string | undefined>());
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
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<BlockId, PageId> 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 ? (
|
||||
<Command.Group
|
||||
heading={t('Find results', { number: resultsPageMeta.length })}
|
||||
>
|
||||
{resultsPageMeta.map(result => {
|
||||
return (
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
router.push(
|
||||
`/public-workspace/${router.query.workspaceId}/${result.id}`
|
||||
);
|
||||
onClose();
|
||||
}}
|
||||
value={result.id}
|
||||
>
|
||||
<StyledListItem>
|
||||
{result.mode === 'edgeless' ? (
|
||||
<EdgelessIcon />
|
||||
) : (
|
||||
<PaperIcon />
|
||||
)}
|
||||
<span>{result.title}</span>
|
||||
</StyledListItem>
|
||||
</Command.Item>
|
||||
);
|
||||
})}
|
||||
</Command.Group>
|
||||
) : (
|
||||
<StyledNotFound>
|
||||
<span>{t('Find 0 result')}</span>
|
||||
<NoResultSVG />
|
||||
</StyledNotFound>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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<SetStateAction<boolean>>;
|
||||
setShowCreatePage: Dispatch<SetStateAction<boolean>>;
|
||||
}) => {
|
||||
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: {
|
||||
<Command.Item
|
||||
key={result.id}
|
||||
onSelect={() => {
|
||||
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();
|
||||
}}
|
||||
>
|
||||
<StyledListItem>
|
||||
|
||||
@@ -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` : '',
|
||||
|
||||
@@ -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 (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['top', 'center']}
|
||||
data-testid="quickSearch"
|
||||
>
|
||||
@@ -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) => {
|
||||
}}
|
||||
>
|
||||
<StyledModalHeader>
|
||||
<Input query={query} setQuery={setQuery} setLoading={setLoading} />
|
||||
<Input
|
||||
open={open}
|
||||
query={query}
|
||||
setQuery={setQuery}
|
||||
setLoading={setLoading}
|
||||
isPublic={isPublic}
|
||||
publishWorkspaceName={publishWorkspaceName}
|
||||
/>
|
||||
<StyledShortcut>{isMac() ? '⌘ + K' : 'Ctrl + K'}</StyledShortcut>
|
||||
</StyledModalHeader>
|
||||
<StyledModalDivider />
|
||||
<StyledModalDivider
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
/>
|
||||
<Command.List>
|
||||
<StyledContent>
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
<StyledContent
|
||||
style={{ display: isPublicAndNoQuery() ? 'none' : '' }}
|
||||
>
|
||||
{!isPublic ? (
|
||||
<Results
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={handleClose}
|
||||
setShowCreatePage={setShowCreatePage}
|
||||
/>
|
||||
) : (
|
||||
<PublishedResults
|
||||
query={query}
|
||||
loading={loading}
|
||||
setLoading={setLoading}
|
||||
onClose={handleClose}
|
||||
setPublishWorkspaceName={setPublishWorkspaceName}
|
||||
data-testid="publishedSearchResults"
|
||||
/>
|
||||
)}
|
||||
</StyledContent>
|
||||
{currentWorkspace?.published ? (
|
||||
<></>
|
||||
) : showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
{!isPublic ? (
|
||||
showCreatePage ? (
|
||||
<>
|
||||
<StyledModalDivider />
|
||||
<StyledModalFooter>
|
||||
<Footer query={query} onClose={handleClose} />
|
||||
</StyledModalFooter>
|
||||
</>
|
||||
) : null
|
||||
) : null}
|
||||
</Command.List>
|
||||
</Command>
|
||||
|
||||
@@ -9,6 +9,7 @@ export const StyledContent = styled('div')(({ theme }) => {
|
||||
marginBottom: '10px',
|
||||
...displayFlex('center', 'flex-start'),
|
||||
color: theme.colors.popoverColor,
|
||||
transition: 'all 0.15s',
|
||||
letterSpacing: '0.06em',
|
||||
'[cmdk-group-heading]': {
|
||||
margin: '5px 16px',
|
||||
@@ -16,7 +17,6 @@ export const StyledContent = styled('div')(({ theme }) => {
|
||||
fontWeight: '500',
|
||||
},
|
||||
'[aria-selected="true"]': {
|
||||
transition: 'background .15s, color .15s',
|
||||
borderRadius: '5px',
|
||||
color: theme.colors.primaryColor,
|
||||
backgroundColor: theme.colors.hoverBackground,
|
||||
@@ -103,6 +103,7 @@ export const StyledModalDivider = styled('div')(({ theme }) => {
|
||||
margin: '6px 16px 6.5px 16px',
|
||||
position: 'relative',
|
||||
borderTop: `0.5px solid ${theme.colors.placeHolderColor}`,
|
||||
transition: 'all 0.15s',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
useWindowsKeyboardShortcuts,
|
||||
useWinMarkdownShortcuts,
|
||||
} from '@/components/shortcuts-modal/config';
|
||||
import Slide from '@mui/material/Slide';
|
||||
import { MuiSlide } from '@/ui/mui';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { getUaHelper } from '@/utils';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
@@ -40,7 +40,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
: windowsKeyboardShortcuts;
|
||||
|
||||
return createPortal(
|
||||
<Slide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<MuiSlide direction="left" in={open} mountOnEnter unmountOnExit>
|
||||
<StyledShortcutsModal data-testid="shortcuts-modal">
|
||||
<>
|
||||
<StyledModalHeader>
|
||||
@@ -81,7 +81,7 @@ export const ShortcutsModal = ({ open, onClose }: ModalProps) => {
|
||||
})}
|
||||
</>
|
||||
</StyledShortcutsModal>
|
||||
</Slide>,
|
||||
</MuiSlide>,
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from 'react';
|
||||
import type { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { WorkspaceAvatar as Avatar } from './Avatar';
|
||||
|
||||
const useAvatar = (workspaceUnit?: WorkspaceUnit) => {
|
||||
const useAvatar = (workspaceUnit: WorkspaceUnit | null) => {
|
||||
const [avatarUrl, setAvatarUrl] = useState('');
|
||||
const avatarId =
|
||||
workspaceUnit?.avatar || workspaceUnit?.blocksuiteWorkspace?.meta.avatar;
|
||||
@@ -23,7 +23,7 @@ const useAvatar = (workspaceUnit?: WorkspaceUnit) => {
|
||||
export const WorkspaceUnitAvatar = ({
|
||||
size = 20,
|
||||
name,
|
||||
workspaceUnit,
|
||||
workspaceUnit = null,
|
||||
style,
|
||||
}: {
|
||||
size?: number;
|
||||
@@ -31,7 +31,7 @@ export const WorkspaceUnitAvatar = ({
|
||||
workspaceUnit?: WorkspaceUnit | null;
|
||||
style?: React.CSSProperties;
|
||||
}) => {
|
||||
const avatarUrl = useAvatar(workspaceUnit || undefined);
|
||||
const avatarUrl = useAvatar(workspaceUnit);
|
||||
return (
|
||||
<Avatar
|
||||
size={size}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { CloudInsyncIcon, LogOutIcon } from '@blocksuite/icons';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
import { WorkspaceAvatar } from '@/components/workspace-avatar';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyledFooter, StyleUserInfo, StyledSignInButton } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
export const Footer = ({
|
||||
onLogin,
|
||||
onLogout,
|
||||
}: {
|
||||
onLogin: () => void;
|
||||
onLogout: () => void;
|
||||
}) => {
|
||||
const { user } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<StyledFooter>
|
||||
{user && (
|
||||
<>
|
||||
<FlexWrapper>
|
||||
<WorkspaceAvatar
|
||||
size={40}
|
||||
name={user.name}
|
||||
avatar={user.avatar}
|
||||
></WorkspaceAvatar>
|
||||
<StyleUserInfo>
|
||||
<p>{user.name}</p>
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
</FlexWrapper>
|
||||
<Tooltip content={t('Sign out')} disablePortal={true}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
onLogout();
|
||||
}}
|
||||
>
|
||||
<LogOutIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!user && (
|
||||
<StyledSignInButton
|
||||
noBorder
|
||||
bold
|
||||
icon={
|
||||
<div className="circle">
|
||||
<CloudInsyncIcon fontSize={16} />
|
||||
</div>
|
||||
}
|
||||
onClick={async () => {
|
||||
onLogin();
|
||||
}}
|
||||
>
|
||||
{t('Sign in')}
|
||||
</StyledSignInButton>
|
||||
)}
|
||||
</StyledFooter>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { styled } from '@/styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
|
||||
const LanguageMenuContent = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const changeLanguage = (event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
export const LanguageMenu = () => {
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
|
||||
return (
|
||||
<Menu
|
||||
content={<LanguageMenuContent />}
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button
|
||||
icon={<ArrowDownIcon />}
|
||||
iconPosition="end"
|
||||
noBorder={true}
|
||||
style={{ textTransform: 'capitalize' }}
|
||||
data-testid="language-menu-button"
|
||||
>
|
||||
{currentLanguage?.originalName}
|
||||
</Button>
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
const ListItem = styled(MenuItem)(({ theme }) => ({
|
||||
height: '38px',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
textTransform: 'capitalize',
|
||||
padding: '0 24px',
|
||||
}));
|
||||
@@ -0,0 +1,83 @@
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import {
|
||||
CloudIcon,
|
||||
LocalIcon,
|
||||
OfflineIcon,
|
||||
PublishedIcon,
|
||||
} from '@/components/workspace-modal/icons';
|
||||
import { UsersIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { StyleWorkspaceInfo, StyleWorkspaceTitle, StyledCard } from './styles';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
|
||||
const WorkspaceType = ({ workspaceData }: { workspaceData: WorkspaceUnit }) => {
|
||||
const { user } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const isOwner = user?.id === workspaceData.owner?.id;
|
||||
|
||||
if (workspaceData.provider === 'local') {
|
||||
return (
|
||||
<p>
|
||||
<LocalIcon />
|
||||
{t('Local Workspace')}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
return isOwner ? (
|
||||
<p>
|
||||
<CloudIcon />
|
||||
{t('Cloud Workspace')}
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
{t('Joined Workspace')}
|
||||
</p>
|
||||
);
|
||||
};
|
||||
|
||||
export const WorkspaceCard = ({
|
||||
workspaceData,
|
||||
onClick,
|
||||
}: {
|
||||
workspaceData: WorkspaceUnit;
|
||||
onClick: (data: WorkspaceUnit) => void;
|
||||
}) => {
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledCard
|
||||
data-testid="workspace-card"
|
||||
onClick={() => {
|
||||
onClick(workspaceData);
|
||||
}}
|
||||
active={workspaceData.id === currentWorkspace?.id}
|
||||
>
|
||||
<FlexWrapper>
|
||||
<WorkspaceUnitAvatar size={58} workspaceUnit={workspaceData} />
|
||||
</FlexWrapper>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>
|
||||
{workspaceData.name || 'AFFiNE'}
|
||||
</StyleWorkspaceTitle>
|
||||
<WorkspaceType workspaceData={workspaceData} />
|
||||
{workspaceData.provider === 'local' && (
|
||||
<p>
|
||||
<OfflineIcon />
|
||||
{t('Available Offline')}
|
||||
</p>
|
||||
)}
|
||||
{workspaceData.published && (
|
||||
<p>
|
||||
<PublishedIcon />
|
||||
{t('Published to Web')}
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
);
|
||||
};
|
||||
@@ -70,7 +70,7 @@ export const CloudIcon = () => {
|
||||
<path
|
||||
d="M2.5 11.3744C2.5 13.837 4.51472 15.8333 7 15.8333L13.75 15.8333C15.8211 15.8333 17.5 14.1532 17.5 12.0807C17.5 10.5419 16.5744 9.12069 15.25 8.54163C15.1098 6.10205 13.07 4.16663 10.5744 4.16663C8.62616 4.16663 6.95578 5.40527 6.25 7.08329C4 7.44788 2.5 9.33336 2.5 11.3744Z"
|
||||
stroke="#60A5FA"
|
||||
stroke-width="1.25"
|
||||
strokeWidth="1.25"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
|
||||
@@ -1,28 +1,34 @@
|
||||
import { styled } from '@/styles';
|
||||
import { Modal, ModalWrapper } from '@/ui/modal';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
import { useState } from 'react';
|
||||
import { CreateWorkspaceModal } from '../create-workspace';
|
||||
import {
|
||||
UsersIcon,
|
||||
AddIcon,
|
||||
LogOutIcon,
|
||||
CloudInsyncIcon,
|
||||
PublishIcon,
|
||||
CloseIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import {
|
||||
WorkspaceAvatar,
|
||||
WorkspaceUnitAvatar,
|
||||
} from '@/components/workspace-avatar';
|
||||
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
|
||||
import { AddIcon, HelpCenterIcon } from '@blocksuite/icons';
|
||||
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { LanguageMenu } from './languageMenu';
|
||||
import { LanguageMenu } from './SelectLanguageMenu';
|
||||
|
||||
import { CloudIcon, LineIcon, LocalIcon, OfflineIcon } from './icons';
|
||||
import { LoginModal } from '../login-modal';
|
||||
import { LogoutModal } from '../logout-modal';
|
||||
import {
|
||||
StyledCard,
|
||||
StyledSplitLine,
|
||||
StyleWorkspaceInfo,
|
||||
StyleWorkspaceTitle,
|
||||
StyledModalHeaderLeft,
|
||||
StyledModalTitle,
|
||||
StyledHelperContainer,
|
||||
StyledModalContent,
|
||||
StyledOperationWrapper,
|
||||
StyleWorkspaceAdd,
|
||||
StyledModalHeader,
|
||||
} from './styles';
|
||||
import { WorkspaceCard } from './WorkspaceCard';
|
||||
import { Footer } from './Footer';
|
||||
interface WorkspaceModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
@@ -30,337 +36,117 @@ interface WorkspaceModalProps {
|
||||
|
||||
export const WorkspaceModal = ({ open, onClose }: WorkspaceModalProps) => {
|
||||
const [createWorkspaceOpen, setCreateWorkspaceOpen] = useState(false);
|
||||
const { workspaceList, currentWorkspace, user, logout, isOwner } =
|
||||
useAppState();
|
||||
const { logout, dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const { t } = useTranslation();
|
||||
const [loginOpen, setLoginOpen] = useState(false);
|
||||
const [logoutOpen, setLogoutOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={720}
|
||||
height={690}
|
||||
style={{
|
||||
padding: '24px 40px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Header>
|
||||
<ContentTitle>{t('My Workspaces')}</ContentTitle>
|
||||
<HeaderOption>
|
||||
<LanguageMenu />
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
border: 'none',
|
||||
margin: '2px 16px',
|
||||
height: '24px',
|
||||
position: 'relative',
|
||||
top: '4px',
|
||||
}}
|
||||
<StyledModalHeader>
|
||||
<StyledModalHeaderLeft>
|
||||
<StyledModalTitle>{t('My Workspaces')}</StyledModalTitle>
|
||||
<Tooltip
|
||||
content={t('Workspace description')}
|
||||
placement="top-start"
|
||||
disablePortal={true}
|
||||
>
|
||||
<LineIcon></LineIcon>
|
||||
</div>
|
||||
<StyledHelperContainer>
|
||||
<HelpCenterIcon />
|
||||
</StyledHelperContainer>
|
||||
</Tooltip>
|
||||
</StyledModalHeaderLeft>
|
||||
|
||||
<Button
|
||||
style={{ border: 'none', padding: 0 }}
|
||||
<StyledOperationWrapper>
|
||||
<LanguageMenu />
|
||||
<StyledSplitLine />
|
||||
<ModalCloseButton
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<CloseIcon></CloseIcon>
|
||||
</Button>
|
||||
</HeaderOption>
|
||||
</Header>
|
||||
<Content>
|
||||
<WorkspaceList>
|
||||
{workspaceList.map((item, index) => {
|
||||
return (
|
||||
<WorkspaceItem
|
||||
onClick={() => {
|
||||
router.replace(`/workspace/${item.id}`);
|
||||
onClose();
|
||||
}}
|
||||
active={item.id === currentWorkspace?.id}
|
||||
key={index}
|
||||
>
|
||||
<div>
|
||||
<WorkspaceUnitAvatar size={58} workspaceUnit={item} />
|
||||
</div>
|
||||
absolute={false}
|
||||
/>
|
||||
</StyledOperationWrapper>
|
||||
</StyledModalHeader>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>
|
||||
{item.name || 'AFFiNE'}
|
||||
</StyleWorkspaceTitle>
|
||||
{isOwner ? (
|
||||
item.provider === 'local' ? (
|
||||
<p>
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
</p>
|
||||
) : (
|
||||
<p>
|
||||
<CloudIcon />
|
||||
Cloud Workspace
|
||||
</p>
|
||||
)
|
||||
) : (
|
||||
<p>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
</p>
|
||||
)}
|
||||
{item.provider === 'local' && (
|
||||
<p>
|
||||
<OfflineIcon />
|
||||
All data can be accessed offline
|
||||
</p>
|
||||
)}
|
||||
{item.published && (
|
||||
<p>
|
||||
<PublishIcon fontSize={16} /> Published to Web
|
||||
</p>
|
||||
)}
|
||||
</StyleWorkspaceInfo>
|
||||
</WorkspaceItem>
|
||||
);
|
||||
})}
|
||||
<WorkspaceItem
|
||||
onClick={() => {
|
||||
setCreateWorkspaceOpen(true);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<AddIcon fontSize={18} />
|
||||
</StyleWorkspaceAdd>
|
||||
</div>
|
||||
<StyledModalContent>
|
||||
{dataCenter.workspaces.map((item, index) => {
|
||||
return (
|
||||
<WorkspaceCard
|
||||
workspaceData={item}
|
||||
onClick={workspaceData => {
|
||||
router.replace(`/workspace/${workspaceData.id}`);
|
||||
onClose();
|
||||
}}
|
||||
key={index}
|
||||
></WorkspaceCard>
|
||||
);
|
||||
})}
|
||||
<StyledCard
|
||||
onClick={() => {
|
||||
setCreateWorkspaceOpen(true);
|
||||
}}
|
||||
>
|
||||
<FlexWrapper>
|
||||
<StyleWorkspaceAdd className="add-icon">
|
||||
<AddIcon fontSize={18} />
|
||||
</StyleWorkspaceAdd>
|
||||
</FlexWrapper>
|
||||
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>New workspace</StyleWorkspaceTitle>
|
||||
<p>Crete or import</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</WorkspaceItem>
|
||||
</WorkspaceList>
|
||||
{/* <p style={{ fontSize: '14px', color: '#ccc', margin: '12px 0' }}>
|
||||
{t('Tips')}
|
||||
{t('Workspace description')}
|
||||
</p> */}
|
||||
</Content>
|
||||
<LoginModal
|
||||
open={loginOpen}
|
||||
onClose={() => {
|
||||
setLoginOpen(false);
|
||||
<StyleWorkspaceInfo>
|
||||
<StyleWorkspaceTitle>{t('New Workspace')}</StyleWorkspaceTitle>
|
||||
<p>{t('Create Or Import')}</p>
|
||||
</StyleWorkspaceInfo>
|
||||
</StyledCard>
|
||||
</StyledModalContent>
|
||||
|
||||
<Footer
|
||||
onLogin={() => {
|
||||
setLoginOpen(true);
|
||||
}}
|
||||
></LoginModal>
|
||||
<Footer>
|
||||
{!user ? (
|
||||
<StyleSignIn
|
||||
onClick={async () => {
|
||||
setLoginOpen(true);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<CloudInsyncIcon fontSize={16} />
|
||||
</span>
|
||||
Sign in to sync with AFFINE Cloud
|
||||
</StyleSignIn>
|
||||
) : (
|
||||
<div style={{ display: 'flex' }}>
|
||||
<div style={{ paddingTop: '20px' }}>
|
||||
<WorkspaceAvatar
|
||||
size={40}
|
||||
name={user.name}
|
||||
avatar={user.avatar}
|
||||
></WorkspaceAvatar>
|
||||
</div>
|
||||
<StyleUserInfo style={{}}>
|
||||
<p>{user.name}</p>
|
||||
<p>{user.email}</p>
|
||||
</StyleUserInfo>
|
||||
<div style={{ paddingTop: '25px' }}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
setLogoutOpen(true);
|
||||
// confirm({
|
||||
// title: 'Sign out?',
|
||||
// content: `All data has been stored in the cloud. `,
|
||||
// confirmText: 'Sign out',
|
||||
// cancelText: 'Cancel',
|
||||
// }).then(async confirm => {
|
||||
// // if (confirm) {
|
||||
// // if (user) {
|
||||
// // await logout();
|
||||
// // router.replace(`/workspace`);
|
||||
// // toast('Enabled success');
|
||||
// // }
|
||||
// // }
|
||||
// });
|
||||
}}
|
||||
>
|
||||
<LogOutIcon></LogOutIcon>
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Footer>
|
||||
<CreateWorkspaceModal
|
||||
open={createWorkspaceOpen}
|
||||
onClose={() => {
|
||||
setCreateWorkspaceOpen(false);
|
||||
onLogout={() => {
|
||||
setLogoutOpen(true);
|
||||
}}
|
||||
></CreateWorkspaceModal>
|
||||
<LogoutModal
|
||||
open={logoutOpen}
|
||||
onClose={async wait => {
|
||||
if (!wait) {
|
||||
await logout();
|
||||
router.replace(`/workspace`);
|
||||
}
|
||||
setLogoutOpen(false);
|
||||
}}
|
||||
></LogoutModal>
|
||||
/>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
<LoginModal
|
||||
open={loginOpen}
|
||||
onClose={() => {
|
||||
setLoginOpen(false);
|
||||
}}
|
||||
/>
|
||||
<LogoutModal
|
||||
open={logoutOpen}
|
||||
onClose={async wait => {
|
||||
if (!wait) {
|
||||
await logout();
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
router.push(`/workspace`);
|
||||
} else {
|
||||
router.push(`/workspace/${dataCenter.workspaces[0].id}`);
|
||||
}
|
||||
}
|
||||
setLogoutOpen(false);
|
||||
}}
|
||||
/>
|
||||
<CreateWorkspaceModal
|
||||
open={createWorkspaceOpen}
|
||||
onClose={() => {
|
||||
setCreateWorkspaceOpen(false);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Header = styled('div')({
|
||||
display: 'flex',
|
||||
});
|
||||
|
||||
const Content = styled('div')({
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '16px',
|
||||
flex: 1,
|
||||
});
|
||||
const HeaderOption = styled.div(() => {
|
||||
return {
|
||||
marginLeft: '16px',
|
||||
};
|
||||
});
|
||||
const ContentTitle = styled('div')({
|
||||
fontSize: '20px',
|
||||
lineHeight: '24px',
|
||||
fontWeight: 600,
|
||||
textAlign: 'left',
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
const WorkspaceList = styled('div')({
|
||||
maxHeight: '500px',
|
||||
overflow: 'auto',
|
||||
display: 'grid',
|
||||
gridRowGap: '24px',
|
||||
gridColumnGap: '24px',
|
||||
fontSize: '16px',
|
||||
marginTop: '36px',
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
});
|
||||
|
||||
export const WorkspaceItem = styled.div<{
|
||||
active?: boolean;
|
||||
}>(({ theme, active }) => {
|
||||
const borderColor = active ? theme.colors.primaryColor : 'transparent';
|
||||
return {
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
height: '124px',
|
||||
boxShadow: theme.shadow.modal,
|
||||
display: 'flex',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${borderColor}`,
|
||||
':hover': {
|
||||
background: theme.colors.hoverBackground,
|
||||
'.add-icon': {
|
||||
border: `1.5px dashed ${theme.colors.primaryColor}`,
|
||||
svg: {
|
||||
fill: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const StyleWorkspaceInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
marginLeft: '16px',
|
||||
p: {
|
||||
fontSize: theme.font.xs,
|
||||
lineHeight: '16px',
|
||||
},
|
||||
svg: {
|
||||
verticalAlign: 'text-bottom',
|
||||
marginRight: '8px',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const StyleWorkspaceTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '8px',
|
||||
};
|
||||
});
|
||||
|
||||
const StyleWorkspaceAdd = styled.div(() => {
|
||||
return {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
textAlign: 'center',
|
||||
background: '#f4f5fa',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
lineHeight: '58px',
|
||||
marginTop: '2px',
|
||||
};
|
||||
});
|
||||
|
||||
const Footer = styled('div')({
|
||||
paddingTop: '16px',
|
||||
});
|
||||
|
||||
const StyleUserInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
marginTop: '16px',
|
||||
flex: 1,
|
||||
p: {
|
||||
lineHeight: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'p:nth-child(1)': {
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const StyleSignIn = styled.div(({ theme }) => {
|
||||
return {
|
||||
cursor: 'pointer',
|
||||
fontSize: '16px',
|
||||
fontWeight: 700,
|
||||
color: theme.colors.iconColor,
|
||||
span: {
|
||||
display: 'inline-block',
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '40px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
textAlign: 'center',
|
||||
lineHeight: '44px',
|
||||
marginRight: '16px',
|
||||
svg: {
|
||||
fill: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
import { LOCALES } from '@affine/i18n';
|
||||
import { TooltipProps } from '@mui/material';
|
||||
import { styled } from '@/styles';
|
||||
import { Tooltip } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { ArrowDownIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
|
||||
export const LanguageMenu = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const changeLanguage = (event: string) => {
|
||||
i18n.changeLanguage(event);
|
||||
};
|
||||
const [show, setShow] = useState(false);
|
||||
const currentLanguage = LOCALES.find(item => item.tag === i18n.language);
|
||||
const [languageName, setLanguageName] = useState(
|
||||
currentLanguage?.originalName
|
||||
);
|
||||
return (
|
||||
<StyledTooltip
|
||||
title={
|
||||
<>
|
||||
{LOCALES.map(option => {
|
||||
return (
|
||||
<ListItem
|
||||
style={{ border: 'none' }}
|
||||
key={option.name}
|
||||
title={option.name}
|
||||
onClick={() => {
|
||||
changeLanguage(option.tag);
|
||||
setShow(false);
|
||||
setLanguageName(option.originalName);
|
||||
}}
|
||||
>
|
||||
{option.originalName}
|
||||
</ListItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
}
|
||||
open={show}
|
||||
>
|
||||
<StyledTitleButton
|
||||
style={{ border: 'none', padding: '0px' }}
|
||||
onClick={() => {
|
||||
setShow(!show);
|
||||
}}
|
||||
>
|
||||
<StyledContainer>
|
||||
<StyledText>{languageName}</StyledText>
|
||||
<ArrowDownIcon fontSize={18} />
|
||||
</StyledContainer>
|
||||
</StyledTitleButton>
|
||||
</StyledTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledContainer = styled('div')(() => ({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}));
|
||||
|
||||
const StyledText = styled('span')(({ theme }) => ({
|
||||
marginRight: '4px',
|
||||
marginLeft: '16px',
|
||||
fontSize: theme.font.sm,
|
||||
fontWeight: '500',
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))(({ theme }) => ({
|
||||
zIndex: theme.zIndex.modal,
|
||||
'& .MuiTooltip-tooltip': {
|
||||
backgroundColor: theme.colors.popoverBackground,
|
||||
boxShadow: theme.shadow.modal,
|
||||
color: theme.colors.popoverColor,
|
||||
},
|
||||
}));
|
||||
|
||||
const ListItem = styled(Button)(({ theme }) => ({
|
||||
display: 'block',
|
||||
width: '100%',
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
textTransform: 'capitalize',
|
||||
}));
|
||||
|
||||
const StyledTitleButton = styled(Button)(({ theme }) => ({
|
||||
color: theme.colors.popoverColor,
|
||||
fontSize: theme.font.sm,
|
||||
}));
|
||||
@@ -1,38 +1,159 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import { displayFlex, displayInlineFlex, styled, textEllipsis } from '@/styles';
|
||||
import { Button } from '@/ui/button';
|
||||
|
||||
export const StyledTitle = styled.div(() => {
|
||||
export const StyledSplitLine = styled.div(({ theme }) => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
fontSize: '20px',
|
||||
fontWeight: 500,
|
||||
marginTop: '60px',
|
||||
lineHeight: 1,
|
||||
width: '1px',
|
||||
height: '20px',
|
||||
background: theme.colors.iconColor,
|
||||
marginRight: '24px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledContent = styled.div(() => {
|
||||
export const StyleWorkspaceInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
padding: '0 40px',
|
||||
marginTop: '32px',
|
||||
fontSize: '18px',
|
||||
lineHeight: '25px',
|
||||
'p:not(last-of-type)': {
|
||||
marginBottom: '10px',
|
||||
marginLeft: '15px',
|
||||
p: {
|
||||
height: '20px',
|
||||
fontSize: theme.font.xs,
|
||||
...displayFlex('flex-start', 'center'),
|
||||
},
|
||||
svg: {
|
||||
marginRight: '10px',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledButton = styled.div(({ theme }) => {
|
||||
export const StyleWorkspaceTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '146px',
|
||||
height: '42px',
|
||||
background: theme.colors.primaryColor,
|
||||
color: '#FFFFFF',
|
||||
fontSize: '18px',
|
||||
fontWeight: 500,
|
||||
borderRadius: '21px',
|
||||
margin: '52px auto 0',
|
||||
fontSize: theme.font.base,
|
||||
fontWeight: 600,
|
||||
lineHeight: '24px',
|
||||
marginBottom: '10px',
|
||||
maxWidth: '200px',
|
||||
...textEllipsis(1),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCard = styled.div<{
|
||||
active?: boolean;
|
||||
}>(({ theme, active }) => {
|
||||
const borderColor = active ? theme.colors.primaryColor : 'transparent';
|
||||
return {
|
||||
width: '310px',
|
||||
height: '124px',
|
||||
cursor: 'pointer',
|
||||
padding: '16px',
|
||||
boxShadow: '0px 0px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '12px',
|
||||
border: `1px solid ${borderColor}`,
|
||||
...displayFlex('flex-start', 'flex-start'),
|
||||
marginBottom: '24px',
|
||||
':hover': {
|
||||
background: theme.colors.hoverBackground,
|
||||
'.add-icon': {
|
||||
border: `1.5px dashed ${theme.colors.primaryColor}`,
|
||||
svg: {
|
||||
fill: theme.colors.primaryColor,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledFooter = styled('div')({
|
||||
height: '84px',
|
||||
padding: '0 40px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
});
|
||||
|
||||
export const StyleUserInfo = styled.div(({ theme }) => {
|
||||
return {
|
||||
textAlign: 'left',
|
||||
marginLeft: '16px',
|
||||
flex: 1,
|
||||
p: {
|
||||
lineHeight: '24px',
|
||||
color: theme.colors.iconColor,
|
||||
},
|
||||
'p:nth-child(1)': {
|
||||
color: theme.colors.textColor,
|
||||
fontWeight: 600,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalHeaderLeft = styled.div(() => {
|
||||
return { ...displayFlex('flex-start', 'center') };
|
||||
});
|
||||
export const StyledModalTitle = styled.div(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledHelperContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
marginLeft: '15px',
|
||||
fontWeight: 400,
|
||||
...displayFlex('center', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalContent = styled('div')({
|
||||
height: '534px',
|
||||
padding: '8px 40px',
|
||||
marginTop: '72px',
|
||||
overflow: 'auto',
|
||||
...displayFlex('space-between', 'flex-start', 'flex-start'),
|
||||
flexWrap: 'wrap',
|
||||
});
|
||||
export const StyledOperationWrapper = styled.div(() => {
|
||||
return {
|
||||
...displayFlex('flex-end', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleWorkspaceAdd = styled.div(() => {
|
||||
return {
|
||||
width: '58px',
|
||||
height: '58px',
|
||||
borderRadius: '100%',
|
||||
textAlign: 'center',
|
||||
background: '#f4f5fa',
|
||||
border: '1.5px dashed #f4f5fa',
|
||||
lineHeight: '58px',
|
||||
marginTop: '2px',
|
||||
};
|
||||
});
|
||||
export const StyledModalHeader = styled('div')(({ theme }) => {
|
||||
return {
|
||||
width: '100%',
|
||||
height: '72px',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
background: theme.colors.pageBackground,
|
||||
borderRadius: '24px 24px 0 0',
|
||||
padding: '0 40px',
|
||||
...displayFlex('space-between', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSignInButton = styled(Button)(({ theme }) => {
|
||||
return {
|
||||
fontWeight: 700,
|
||||
paddingLeft: 0,
|
||||
'.circle': {
|
||||
width: '40px',
|
||||
height: '40px',
|
||||
borderRadius: '20px',
|
||||
backgroundColor: theme.colors.innerHoverBackground,
|
||||
flexShrink: 0,
|
||||
marginRight: '16px',
|
||||
...displayInlineFlex('center', 'center'),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,24 +1,16 @@
|
||||
import { styled } from '@/styles';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
export const ExportPageTitleContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
|
||||
fontWeight: '500',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { Button } from '@/ui/button';
|
||||
export const ExportPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { t } = useTranslation();
|
||||
console.log(workspace);
|
||||
return (
|
||||
<ExportPageTitleContainer>
|
||||
<Trans i18nKey="Export Workspace">
|
||||
Export Workspace
|
||||
<code style={{ margin: '0 10px' }}>
|
||||
{{ workspace: workspace.name }}
|
||||
</code>
|
||||
Is Comming
|
||||
</Trans>
|
||||
</ExportPageTitleContainer>
|
||||
<>
|
||||
<Wrapper marginBottom="32px"> {t('Export Description')}</Wrapper>
|
||||
<Button type="light" shape="circle" disabled>
|
||||
{t('Export AFFINE backup file')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
import {
|
||||
StyledCopyButtonContainer,
|
||||
StyledPublishContent,
|
||||
StyledPublishCopyContainer,
|
||||
StyledPublishExplanation,
|
||||
StyledSettingH2,
|
||||
StyledStopPublishContainer,
|
||||
} from './style';
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { toast } from '@/ui/toast';
|
||||
// import { useAppState } from '@/providers/app-state-provider3';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EnableWorkspaceButton } from '../enable-workspace';
|
||||
import { Wrapper, Content, FlexWrapper } from '@/ui/layout';
|
||||
export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const shareUrl = window.location.host + '/public-workspace/' + workspace.id;
|
||||
const { publishWorkspace } = useWorkspaceHelper();
|
||||
@@ -23,95 +15,75 @@ export const PublishPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const togglePublic = async (flag: boolean) => {
|
||||
try {
|
||||
await publishWorkspace(workspace.id.toString(), flag);
|
||||
setLoaded(false);
|
||||
} catch (e) {
|
||||
toast('Failed to publish workspace');
|
||||
toast(t('Failed to publish workspace'));
|
||||
}
|
||||
};
|
||||
|
||||
const copyUrl = () => {
|
||||
navigator.clipboard.writeText(shareUrl);
|
||||
toast('Copied url to clipboard');
|
||||
toast(t('Copied link to clipboard'));
|
||||
};
|
||||
|
||||
if (workspace.provider === 'affine') {
|
||||
if (workspace.published) {
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="32px">{t('Published Description')}</Wrapper>
|
||||
|
||||
<Wrapper marginBottom="12px">
|
||||
<Content weight="500">{t('Share with link')}</Content>
|
||||
</Wrapper>
|
||||
<FlexWrapper>
|
||||
<Input width={582} value={shareUrl} disabled={true}></Input>
|
||||
<Button
|
||||
onClick={copyUrl}
|
||||
type="light"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
>
|
||||
{t('Copy Link')}
|
||||
</Button>
|
||||
</FlexWrapper>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(false);
|
||||
}}
|
||||
loading={false}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
style={{ marginTop: '38px' }}
|
||||
>
|
||||
{t('Stop publishing')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wrapper marginBottom="32px">{t('Publishing Description')}</Wrapper>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(true);
|
||||
}}
|
||||
loading={loaded}
|
||||
type="light"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Publish to web')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{workspace.provider === 'affine' ? (
|
||||
<div
|
||||
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<StyledPublishContent>
|
||||
{workspace.published ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
The current workspace has been published to the web, everyone
|
||||
can view the contents of this workspace through the link.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<StyledPublishCopyContainer>
|
||||
<StyledSettingH2 marginBottom={16}>
|
||||
{t('Share with link')}
|
||||
</StyledSettingH2>
|
||||
<Input width={500} value={shareUrl} disabled={true}></Input>
|
||||
<StyledCopyButtonContainer>
|
||||
<Button onClick={copyUrl} type="primary" shape="circle">
|
||||
{t('Copy Link')}
|
||||
</Button>
|
||||
</StyledCopyButtonContainer>
|
||||
</StyledPublishCopyContainer>
|
||||
</>
|
||||
) : (
|
||||
<StyledPublishExplanation>
|
||||
{t('Publishing Description')}
|
||||
<div style={{ marginTop: '64px' }}>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(true);
|
||||
setLoaded(false);
|
||||
}}
|
||||
loading={loaded}
|
||||
type="primary"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Publish to web')}
|
||||
</Button>
|
||||
</div>
|
||||
</StyledPublishExplanation>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
|
||||
{workspace.published ? (
|
||||
<StyledStopPublishContainer>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setLoaded(true);
|
||||
await togglePublic(false);
|
||||
setLoaded(true);
|
||||
}}
|
||||
loading={false}
|
||||
type="danger"
|
||||
shape="circle"
|
||||
>
|
||||
{t('Stop publishing')}
|
||||
</Button>
|
||||
</StyledStopPublishContainer>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<StyledPublishContent>
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
Publishing to web requires AFFiNE Cloud service.
|
||||
</StyledPublishExplanation>
|
||||
|
||||
<div style={{ marginTop: '72px' }}>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</div>
|
||||
</>
|
||||
</StyledPublishContent>
|
||||
)}
|
||||
<Wrapper marginBottom="32px">{t('Publishing')}</Wrapper>
|
||||
<EnableWorkspaceButton />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,84 +1,106 @@
|
||||
import {
|
||||
StyleAsync,
|
||||
StyledPublishContent,
|
||||
StyledPublishExplanation,
|
||||
StyledWorkspaceName,
|
||||
StyledWorkspaceType,
|
||||
StyledEmail,
|
||||
// StyledDownloadCard,
|
||||
// StyledDownloadCardDes,
|
||||
} from './style';
|
||||
import { DownloadIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@/ui/button';
|
||||
import { Menu, MenuItem } from '@/ui/menu';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
import { useTranslation, Trans } from '@affine/i18n';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { EnableWorkspaceButton } from '../enable-workspace';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { FlexWrapper, Content, Wrapper } from '@/ui/layout';
|
||||
|
||||
// // FIXME: Temporary solution, since the @blocksuite/icons is broken
|
||||
// const ActiveIcon = () => {
|
||||
// return (
|
||||
// <svg
|
||||
// width="24"
|
||||
// height="24"
|
||||
// viewBox="0 0 24 24"
|
||||
// fill="none"
|
||||
// xmlns="http://www.w3.org/2000/svg"
|
||||
// >
|
||||
// <path
|
||||
// fillRule="evenodd"
|
||||
// clipRule="evenodd"
|
||||
// d="M2.25 12C2.25 6.61522 6.61522 2.25 12 2.25C17.3848 2.25 21.75 6.61522 21.75 12C21.75 17.3848 17.3848 21.75 12 21.75C6.61522 21.75 2.25 17.3848 2.25 12Z"
|
||||
// fill="#6880FF"
|
||||
// />
|
||||
// <path
|
||||
// fillRule="evenodd"
|
||||
// clipRule="evenodd"
|
||||
// d="M16.5068 8.44714C16.8121 8.72703 16.8328 9.20146 16.5529 9.5068L11.0529 15.5068C10.9146 15.6576 10.7208 15.7454 10.5163 15.7498C10.3118 15.7543 10.1143 15.675 9.96967 15.5303L7.46967 13.0303C7.17678 12.7374 7.17678 12.2626 7.46967 11.9697C7.76256 11.6768 8.23744 11.6768 8.53033 11.9697L10.4764 13.9158L15.4471 8.49321C15.727 8.18787 16.2015 8.16724 16.5068 8.44714Z"
|
||||
// fill="white"
|
||||
// />
|
||||
// </svg>
|
||||
// );
|
||||
// };
|
||||
|
||||
export const SyncPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { t } = useTranslation();
|
||||
const { user } = useAppState();
|
||||
if (workspace.provider === 'local') {
|
||||
return (
|
||||
<>
|
||||
<FlexWrapper alignItems="center" style={{ marginBottom: '12px' }}>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<Content weight={500}>{t('is a Local Workspace')}</Content>
|
||||
</FlexWrapper>
|
||||
<p>{t('Local Workspace Description')}</p>
|
||||
<Wrapper marginTop="32px">
|
||||
<EnableWorkspaceButton />
|
||||
</Wrapper>
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<StyledPublishContent>
|
||||
{workspace.provider === 'local' ? (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name};</StyledWorkspaceName>
|
||||
<StyledWorkspaceType>is a Local Workspace.</StyledWorkspaceType>
|
||||
</StyledPublishExplanation>
|
||||
<StyledWorkspaceType>
|
||||
All data is stored on the current device. You can enable AFFiNE
|
||||
Cloud for this workspace to keep data in sync with the cloud.
|
||||
</StyledWorkspaceType>
|
||||
<StyleAsync>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</StyleAsync>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StyledPublishExplanation>
|
||||
<Trans i18nKey="Sync Description2">
|
||||
<code>{{ workspaceName: workspace.name ?? 'Affine' }}</code>
|
||||
is Cloud Workspace. All data will be synchronised and saved to
|
||||
the AFFiNE
|
||||
</Trans>
|
||||
</StyledPublishExplanation>
|
||||
<StyleAsync>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'core' })}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
// deleteMember(workspace.id, 0);
|
||||
}}
|
||||
icon={<DownloadIcon />}
|
||||
>
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<Button type="primary">
|
||||
{t('Download data to device', { CoreOrAll: 'all' })}
|
||||
</Button>
|
||||
</Menu>
|
||||
</StyleAsync>
|
||||
</>
|
||||
)}
|
||||
</StyledPublishContent>
|
||||
</div>
|
||||
<>
|
||||
<FlexWrapper alignItems="center" style={{ marginBottom: '12px' }}>
|
||||
<WorkspaceUnitAvatar
|
||||
size={32}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
style={{ marginRight: '12px' }}
|
||||
/>
|
||||
<StyledWorkspaceName>{workspace.name} </StyledWorkspaceName>
|
||||
<Content weight={500}>{t('is a Cloud Workspace')}</Content>
|
||||
</FlexWrapper>
|
||||
<Trans i18nKey="Cloud Workspace Description">
|
||||
All data will be synchronised and saved to the AFFiNE account
|
||||
<StyledEmail>
|
||||
{{
|
||||
email: '{' + user?.email + '}.',
|
||||
}}
|
||||
</StyledEmail>
|
||||
</Trans>
|
||||
|
||||
{/*<Wrapper marginBottom="12px" marginTop="32px">*/}
|
||||
{/* <Content weight="500">{t('Data sync mode')}</Content>*/}
|
||||
{/*</Wrapper>*/}
|
||||
{/*<FlexWrapper>*/}
|
||||
{/* <StyledDownloadCard>*/}
|
||||
{/* <ActiveIcon />*/}
|
||||
{/* <Wrapper>{t('Download all data')}</Wrapper>*/}
|
||||
{/* <StyledDownloadCardDes>*/}
|
||||
{/* {t('It takes up more space on your device.')}*/}
|
||||
{/* </StyledDownloadCardDes>*/}
|
||||
{/* </StyledDownloadCard>*/}
|
||||
|
||||
{/* <StyledDownloadCard active>*/}
|
||||
{/* <ActiveIcon />*/}
|
||||
{/* <Wrapper>{t('Download core data')}</Wrapper>*/}
|
||||
{/* <StyledDownloadCardDes>*/}
|
||||
{/* {t('It takes up little space on your device.')}*/}
|
||||
{/* </StyledDownloadCardDes>*/}
|
||||
{/* </StyledDownloadCard>*/}
|
||||
{/*</FlexWrapper>*/}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,13 @@
|
||||
import {
|
||||
StyledDeleteButtonContainer,
|
||||
// StyledSettingAvatar,
|
||||
StyledSettingAvatarContent,
|
||||
StyledSettingInputContainer,
|
||||
StyledDoneButtonContainer,
|
||||
StyledInput,
|
||||
StyledProviderInfo,
|
||||
StyleGeneral,
|
||||
StyledAvatar,
|
||||
} from './style';
|
||||
import { StyledSettingH2 } from '../style';
|
||||
import { StyledInput, StyledProviderInfo, StyledAvatar } from './style';
|
||||
import { StyledSettingKey, StyledRow } from '../style';
|
||||
import { FlexWrapper, Content } from '@/ui/layout';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { WorkspaceDelete } from './delete';
|
||||
import { WorkspaceLeave } from './leave';
|
||||
import { DoneIcon, UsersIcon } from '@blocksuite/icons';
|
||||
// import { Upload } from '@/components/file-upload';
|
||||
import { UsersIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
@@ -33,9 +23,6 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const { updateWorkspace } = useWorkspaceHelper();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChangeWorkSpaceName = (newName: string) => {
|
||||
setWorkspaceName(newName);
|
||||
};
|
||||
const handleUpdateWorkspaceName = () => {
|
||||
currentWorkspace &&
|
||||
updateWorkspace({ name: workspaceName }, currentWorkspace);
|
||||
@@ -47,83 +34,105 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
(await updateWorkspace({ avatarBlob: blob }, currentWorkspace));
|
||||
};
|
||||
|
||||
return workspace ? (
|
||||
<StyleGeneral>
|
||||
<div style={{ flex: 1, overflow: 'auto' }}>
|
||||
<StyledSettingH2>Workspace Avatar</StyledSettingH2>
|
||||
<StyledSettingAvatarContent>
|
||||
<StyledAvatar>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon">
|
||||
<CameraIcon></CameraIcon>
|
||||
</div>
|
||||
<WorkspaceUnitAvatar
|
||||
size={60}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</StyledAvatar>
|
||||
{/* TODO: Wait for image sync to complete */}
|
||||
{/* <Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<Button loading={uploading}>{t('Upload')}</Button>
|
||||
</Upload> */}
|
||||
{/* TODO: add upload logic */}
|
||||
</StyledSettingAvatarContent>
|
||||
<StyledSettingH2 marginTop={20}>{t('Workspace Name')}</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
if (!workspace) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Avatar')}</StyledSettingKey>
|
||||
<StyledAvatar>
|
||||
<Upload
|
||||
accept="image/gif,image/jpeg,image/jpg,image/png,image/svg"
|
||||
fileChange={fileChange}
|
||||
>
|
||||
<>
|
||||
<div className="camera-icon">
|
||||
<CameraIcon></CameraIcon>
|
||||
</div>
|
||||
<WorkspaceUnitAvatar
|
||||
size={72}
|
||||
name={workspace.name}
|
||||
workspaceUnit={workspace}
|
||||
/>
|
||||
</>
|
||||
</Upload>
|
||||
</StyledAvatar>
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Name')}</StyledSettingKey>
|
||||
<FlexWrapper>
|
||||
<StyledInput
|
||||
width={284}
|
||||
height={32}
|
||||
height={38}
|
||||
value={workspaceName}
|
||||
placeholder={t('Workspace Name')}
|
||||
maxLength={14}
|
||||
minLength={1}
|
||||
maxLength={15}
|
||||
minLength={0}
|
||||
disabled={!isOwner}
|
||||
onChange={handleChangeWorkSpaceName}
|
||||
onChange={newName => {
|
||||
setWorkspaceName(newName);
|
||||
}}
|
||||
></StyledInput>
|
||||
{isOwner ? (
|
||||
<StyledDoneButtonContainer
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName();
|
||||
}}
|
||||
>
|
||||
<DoneIcon />
|
||||
</StyledDoneButtonContainer>
|
||||
) : null}
|
||||
</StyledSettingInputContainer>
|
||||
<StyledSettingH2 marginTop={20}>{t('Workspace Type')}</StyledSettingH2>
|
||||
<StyledSettingInputContainer>
|
||||
{isOwner && (
|
||||
<>
|
||||
<Button
|
||||
type="default"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
onClick={() => {
|
||||
setWorkspaceName(workspace.name);
|
||||
}}
|
||||
>
|
||||
{t('Cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
type="light"
|
||||
shape="circle"
|
||||
style={{ marginLeft: '24px' }}
|
||||
onClick={() => {
|
||||
handleUpdateWorkspaceName();
|
||||
}}
|
||||
>
|
||||
{t('Confirm')}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</FlexWrapper>
|
||||
</StyledRow>
|
||||
|
||||
<StyledRow>
|
||||
<StyledSettingKey>{t('Workspace Type')}</StyledSettingKey>
|
||||
<FlexWrapper>
|
||||
{isOwner ? (
|
||||
currentWorkspace?.provider === 'local' ? (
|
||||
<StyledProviderInfo>
|
||||
<FlexWrapper alignItems="center">
|
||||
<LocalIcon />
|
||||
Local Workspace
|
||||
</StyledProviderInfo>
|
||||
<Content style={{ marginLeft: '15px' }}>
|
||||
{t('Local Workspace')}
|
||||
</Content>
|
||||
</FlexWrapper>
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<FlexWrapper alignItems="center">
|
||||
<CloudIcon />
|
||||
All data can be accessed offline
|
||||
</StyledProviderInfo>
|
||||
<Content style={{ marginLeft: '15px' }}>
|
||||
{t('Available Offline')}
|
||||
</Content>
|
||||
</FlexWrapper>
|
||||
)
|
||||
) : (
|
||||
<StyledProviderInfo>
|
||||
<UsersIcon fontSize={20} color={'#FF646B'} />
|
||||
Joined Workspace
|
||||
{t('Joined Workspace')}
|
||||
</StyledProviderInfo>
|
||||
)}
|
||||
</StyledSettingInputContainer>
|
||||
</div>
|
||||
</FlexWrapper>
|
||||
</StyledRow>
|
||||
|
||||
<StyledDeleteButtonContainer>
|
||||
<StyledRow>
|
||||
<StyledSettingKey> {t('Delete Workspace')}</StyledSettingKey>
|
||||
{isOwner ? (
|
||||
<>
|
||||
<Button
|
||||
@@ -163,7 +172,7 @@ export const GeneralPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledDeleteButtonContainer>
|
||||
</StyleGeneral>
|
||||
) : null;
|
||||
</StyledRow>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useRouter } from 'next/router';
|
||||
import Modal from '@/ui/modal';
|
||||
import Input from '@/ui/input';
|
||||
import {
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
import { useState } from 'react';
|
||||
import { ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { Trans, useTranslation } from '@affine/i18n';
|
||||
|
||||
@@ -1,40 +1,6 @@
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import IconButton from '@/ui/button/IconButton';
|
||||
import { styled } from '@/styles';
|
||||
import Input from '@/ui/input';
|
||||
|
||||
export const StyledSettingInputContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '12px',
|
||||
width: '100%',
|
||||
...displayFlex('flex-start', 'center'),
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledDeleteButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
};
|
||||
});
|
||||
export const StyleGeneral = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
};
|
||||
});
|
||||
export const StyledDoneButtonContainer = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
border: `1px solid ${theme.colors.borderColor}`,
|
||||
borderRadius: '10px',
|
||||
height: '32px',
|
||||
overflow: 'hidden',
|
||||
marginLeft: '20px',
|
||||
':hover': {
|
||||
borderColor: theme.colors.primaryColor,
|
||||
},
|
||||
};
|
||||
});
|
||||
export const StyledInput = styled(Input)(({ theme }) => {
|
||||
return {
|
||||
border: `1px solid ${theme.colors.borderColor}`,
|
||||
@@ -43,24 +9,9 @@ export const StyledInput = styled(Input)(({ theme }) => {
|
||||
};
|
||||
});
|
||||
|
||||
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' };
|
||||
});
|
||||
|
||||
export const StyledProviderInfo = styled('p')(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: theme.font.sm,
|
||||
svg: {
|
||||
verticalAlign: 'sub',
|
||||
marginRight: '10px',
|
||||
@@ -71,7 +22,6 @@ export const StyledProviderInfo = styled('p')(({ theme }) => {
|
||||
export const StyledAvatar = styled('div')(() => {
|
||||
return {
|
||||
position: 'relative',
|
||||
marginRight: '20px',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
'.camera-icon': {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Modal, ModalWrapper, ModalCloseButton } from '@/ui/modal';
|
||||
import { Button } from '@/ui/button';
|
||||
import Input from '@/ui/input';
|
||||
import { useState } from 'react';
|
||||
import { Avatar } from '@mui/material';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import useMembers from '@/hooks/use-members';
|
||||
import { User } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
@@ -76,10 +76,9 @@ export const InviteMemberModal = ({
|
||||
<ModalWrapper width={460} height={236}>
|
||||
<Header>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={6}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
setEmail('');
|
||||
}}
|
||||
/>
|
||||
</Header>
|
||||
@@ -102,7 +101,7 @@ export const InviteMemberModal = ({
|
||||
) : (
|
||||
<Member>
|
||||
{userData?.avatar ? (
|
||||
<Avatar src={userData?.avatar}></Avatar>
|
||||
<MuiAvatar src={userData?.avatar}></MuiAvatar>
|
||||
) : (
|
||||
<MemberIcon>
|
||||
<EmailIcon></EmailIcon>
|
||||
@@ -125,6 +124,7 @@ export const InviteMemberModal = ({
|
||||
style={{ width: '364px', height: '38px', borderRadius: '40px' }}
|
||||
onClick={async () => {
|
||||
await inviteMember(email);
|
||||
setEmail('');
|
||||
onInviteSuccess();
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -10,10 +10,9 @@ import {
|
||||
StyledMemberRoleContainer,
|
||||
StyledMemberTitleContainer,
|
||||
StyledMoreVerticalButton,
|
||||
StyledPublishExplanation,
|
||||
StyledMemberWarp,
|
||||
StyledMemberContainer,
|
||||
} from './style';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { MoreVerticalIcon, EmailIcon, TrashIcon } from '@blocksuite/icons';
|
||||
import { useState } from 'react';
|
||||
import { Button, IconButton } from '@/ui/button';
|
||||
@@ -25,14 +24,13 @@ import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { toast } from '@/ui/toast';
|
||||
import useMembers from '@/hooks/use-members';
|
||||
import Loading from '@/components/loading';
|
||||
import { Wrapper } from '@/ui/layout';
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { EnableWorkspaceButton } from '@/components/enable-workspace';
|
||||
|
||||
export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
const [isInviteModalShow, setIsInviteModalShow] = useState(false);
|
||||
const { members, removeMember, loaded } = useMembers();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
@@ -41,9 +39,9 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledMemberContainer>
|
||||
<StyledMemberListContainer>
|
||||
{!loaded && (
|
||||
<Wrapper justifyContent="center">
|
||||
<FlexWrapper justifyContent="center">
|
||||
<Loading size={25} />
|
||||
</Wrapper>
|
||||
</FlexWrapper>
|
||||
)}
|
||||
{loaded && members.length === 0 && (
|
||||
<Empty width={648} sx={{ marginTop: '60px' }} height={300} />
|
||||
@@ -89,42 +87,50 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
<StyledMemberRoleContainer>
|
||||
{member.accepted
|
||||
? member.type !== 99
|
||||
? 'Member'
|
||||
: 'Workspace Owner'
|
||||
: 'Pending'}
|
||||
? t('Member')
|
||||
: t('Owner')
|
||||
: t('Pending')}
|
||||
</StyledMemberRoleContainer>
|
||||
<StyledMoreVerticalButton>
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const confirmRemove = await confirm({
|
||||
title: 'Delete Member?',
|
||||
content: `will delete member`,
|
||||
confirmText: 'Delete',
|
||||
confirmType: 'danger',
|
||||
});
|
||||
{member.type === 99 ? (
|
||||
<></>
|
||||
) : (
|
||||
<Menu
|
||||
content={
|
||||
<>
|
||||
<MenuItem
|
||||
onClick={async () => {
|
||||
const confirmRemove = await confirm({
|
||||
title: t('Delete Member?'),
|
||||
content: t('will delete member'),
|
||||
confirmText: t('Delete'),
|
||||
confirmType: 'danger',
|
||||
});
|
||||
|
||||
if (!confirmRemove) {
|
||||
return;
|
||||
}
|
||||
await removeMember(member.id);
|
||||
toast(`${user.name} has been removed`);
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
if (!confirmRemove) {
|
||||
return;
|
||||
}
|
||||
await removeMember(member.id);
|
||||
toast(
|
||||
t('Member has been removed', {
|
||||
name: user.name,
|
||||
})
|
||||
);
|
||||
}}
|
||||
icon={<TrashIcon />}
|
||||
>
|
||||
{t('Delete')}
|
||||
</MenuItem>
|
||||
</>
|
||||
}
|
||||
placement="bottom-end"
|
||||
disablePortal={true}
|
||||
>
|
||||
<IconButton>
|
||||
<MoreVerticalIcon />
|
||||
</IconButton>
|
||||
</Menu>
|
||||
)}
|
||||
</StyledMoreVerticalButton>
|
||||
</StyledMemberListItem>
|
||||
);
|
||||
@@ -159,11 +165,14 @@ export const MembersPage = ({ workspace }: { workspace: WorkspaceUnit }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledMemberWarp>
|
||||
{t('Collaboration Description')}
|
||||
<StyledPublishExplanation>
|
||||
<EnableWorkspaceButton></EnableWorkspaceButton>
|
||||
</StyledPublishExplanation>
|
||||
</StyledMemberWarp>
|
||||
<Wrapper
|
||||
style={{
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
}}
|
||||
>
|
||||
<Wrapper marginBottom="32px">{t('Collaboration Description')}</Wrapper>
|
||||
<EnableWorkspaceButton />
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
|
||||
export const StyledMemberTitleContainer = styled('li')(() => {
|
||||
return {
|
||||
@@ -97,24 +97,3 @@ export const StyledMoreVerticalButton = styled('button')(() => {
|
||||
paddingRight: '48px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
flex: 1,
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledMemberWarp = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: '0 0 48px 0',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { styled } from '@/styles';
|
||||
|
||||
import { FlexWrapper } from '@/ui/layout';
|
||||
export const StyledSettingContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
@@ -13,41 +13,19 @@ export const StyledSettingContainer = styled('div')(() => {
|
||||
export const StyledSettingSidebar = styled('div')(() => {
|
||||
{
|
||||
return {
|
||||
// height: '48px',
|
||||
marginTop: '50px',
|
||||
marginTop: '48px',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingContent = styled('div')(() => {
|
||||
return {
|
||||
overflow: 'hidden',
|
||||
overflow: 'auto',
|
||||
flex: 1,
|
||||
paddingTop: '48px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSetting = styled('div')(({ theme }) => {
|
||||
{
|
||||
return {
|
||||
width: '236px',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingSidebarHeader = styled('div')(() => {
|
||||
{
|
||||
return {
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
textAlign: 'left',
|
||||
marginTop: '37px',
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const StyledSettingTabContainer = styled('ul')(() => {
|
||||
{
|
||||
return {
|
||||
@@ -65,8 +43,8 @@ export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
height: '34px',
|
||||
color: isActive ? theme.colors.primaryColor : theme.colors.textColor,
|
||||
fontWeight: '500',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
fontSize: theme.font.h6,
|
||||
lineHeight: theme.font.lineHeight,
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.15s ease',
|
||||
borderBottom: `2px solid ${
|
||||
@@ -78,79 +56,60 @@ export const WorkspaceSettingTagItem = styled('li')<{ isActive?: boolean }>(
|
||||
}
|
||||
);
|
||||
|
||||
export const StyledSettingTagIconContainer = styled('div')(() => {
|
||||
export const StyledSettingKey = styled.div(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledSettingH2 = styled('h2')<{
|
||||
marginTop?: number;
|
||||
marginBottom?: number;
|
||||
}>(({ marginTop, marginBottom, theme }) => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
width: '140px',
|
||||
textAlign: 'right',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeightBase,
|
||||
marginTop: marginTop ? `${marginTop}px` : '0px',
|
||||
marginBottom: marginBottom ? `${marginBottom}px` : '0px',
|
||||
fontWeight: 500,
|
||||
marginRight: '56px',
|
||||
flexShrink: 0,
|
||||
};
|
||||
});
|
||||
export const StyledRow = styled(FlexWrapper)(() => {
|
||||
return {
|
||||
marginBottom: '32px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishExplanation = styled('div')(() => {
|
||||
return {
|
||||
paddingRight: '48px',
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
marginBottom: '22px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceName = styled('span')(() => {
|
||||
export const StyledWorkspaceName = styled('span')(({ theme }) => {
|
||||
return {
|
||||
fontWeight: '400',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
};
|
||||
});
|
||||
export const StyledWorkspaceType = styled('span')(() => {
|
||||
return {
|
||||
fontWeight: '500',
|
||||
fontSize: '18px',
|
||||
lineHeight: '26px',
|
||||
fontSize: theme.font.h6,
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishCopyContainer = styled('div')(() => {
|
||||
export const StyledEmail = styled('span')(() => {
|
||||
return {
|
||||
marginTop: '12px',
|
||||
marginBottom: '20px',
|
||||
paddingTop: '20px',
|
||||
flex: 1,
|
||||
};
|
||||
});
|
||||
export const StyledStopPublishContainer = styled('div')(() => {
|
||||
return {
|
||||
textAlign: 'center',
|
||||
color: '#E8178A',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledCopyButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledPublishContent = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyleAsync = styled('div')(() => {
|
||||
return {
|
||||
marginTop: '64px',
|
||||
};
|
||||
});
|
||||
// export const StyledDownloadCard = styled.div<{ active?: boolean }>(
|
||||
// ({ theme, active }) => {
|
||||
// return {
|
||||
// width: '240px',
|
||||
// height: '86px',
|
||||
// border: '1px solid',
|
||||
// borderColor: active
|
||||
// ? theme.colors.primaryColor
|
||||
// : theme.colors.borderColor,
|
||||
// borderRadius: '10px',
|
||||
// padding: '8px 12px',
|
||||
// position: 'relative',
|
||||
// ':not(:last-of-type)': {
|
||||
// marginRight: '24px',
|
||||
// },
|
||||
// svg: {
|
||||
// display: active ? 'block' : 'none',
|
||||
// ...positionAbsolute({ top: '-12px', right: '-12px' }),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
// );
|
||||
// export const StyledDownloadCardDes = styled.div(({ theme }) => {
|
||||
// return {
|
||||
// fontSize: theme.font.sm,
|
||||
// color: theme.colors.iconColor,
|
||||
// };
|
||||
// });
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import { styled } from '@/styles';
|
||||
|
||||
export const WorkspaceItemWrapper = styled('div')(({ theme }) => ({
|
||||
|
||||
+2
-2
@@ -32,13 +32,13 @@ export const WorkspaceSelector = () => {
|
||||
>
|
||||
<WorkspaceUnitAvatar
|
||||
size={28}
|
||||
name={currentWorkspace?.name ?? 'AFFiNE'}
|
||||
name={currentWorkspace?.name ?? 'AFFiNE Test'}
|
||||
workspaceUnit={currentWorkspace}
|
||||
/>
|
||||
</div>
|
||||
</Avatar>
|
||||
<WorkspaceName data-testid="workspace-name">
|
||||
{currentWorkspace?.name ?? 'AFFiNE'}
|
||||
{currentWorkspace?.name ?? 'AFFiNE Test'}
|
||||
</WorkspaceName>
|
||||
</SelectorWrapper>
|
||||
<WorkspaceModal
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import MuiAvatar from '@mui/material/Avatar';
|
||||
import { MuiAvatar } from '@/ui/mui';
|
||||
import { styled } from '@/styles';
|
||||
import { StyledPopperContainer } from '@/ui/shared/Container';
|
||||
|
||||
|
||||
@@ -11,18 +11,17 @@ import {
|
||||
StyledSubListItem,
|
||||
} from './style';
|
||||
import { Arrow } from './icons';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import {
|
||||
ArrowDownIcon,
|
||||
SearchIcon,
|
||||
AllPagesIcon,
|
||||
FavouritesIcon,
|
||||
ImportIcon,
|
||||
TrashIcon,
|
||||
AddIcon,
|
||||
SettingsIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import Link from 'next/link';
|
||||
import { MuiCollapse } from '@/ui/mui';
|
||||
import { Tooltip } from '@/ui/tooltip';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -39,7 +38,7 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
const favoriteList = pageList.filter(p => p.favorite && !p.trash);
|
||||
return (
|
||||
<Collapse in={showList}>
|
||||
<MuiCollapse in={showList}>
|
||||
{favoriteList.map((pageMeta, index) => {
|
||||
const active = router.query.pageId === pageMeta.id;
|
||||
return (
|
||||
@@ -61,11 +60,11 @@ const FavoriteList = ({ showList }: { showList: boolean }) => {
|
||||
{favoriteList.length === 0 && (
|
||||
<StyledSubListItem disable={true}>{t('No item')}</StyledSubListItem>
|
||||
)}
|
||||
</Collapse>
|
||||
</MuiCollapse>
|
||||
);
|
||||
};
|
||||
export const WorkSpaceSliderBar = () => {
|
||||
const { triggerQuickSearchModal, triggerImportModal } = useModal();
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
const [showSubFavorite, setShowSubFavorite] = useState(true);
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { openPage, createPage } = usePageHelper();
|
||||
@@ -161,14 +160,14 @@ export const WorkSpaceSliderBar = () => {
|
||||
setShowWorkspaceSetting(false);
|
||||
}}
|
||||
/> */}
|
||||
|
||||
<StyledListItem
|
||||
{/* TODO: will finish the feature next version */}
|
||||
{/* <StyledListItem
|
||||
onClick={() => {
|
||||
triggerImportModal();
|
||||
}}
|
||||
>
|
||||
<ImportIcon /> {t('Import')}
|
||||
</StyledListItem>
|
||||
</StyledListItem> */}
|
||||
|
||||
<Link href={{ pathname: paths.trash }}>
|
||||
<StyledListItem active={router.asPath === paths.trash}>
|
||||
|
||||
@@ -4,9 +4,9 @@ import Link from 'next/link';
|
||||
export const StyledSliderBar = styled.div<{ show: boolean }>(
|
||||
({ theme, show }) => {
|
||||
return {
|
||||
width: show ? '320px' : '0',
|
||||
width: show ? '256px' : '0',
|
||||
height: '100vh',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#FBFBFC',
|
||||
background: theme.mode === 'dark' ? '#272727' : '#F9F9FB',
|
||||
boxShadow: theme.shadow.modal,
|
||||
transition: 'width .15s, padding .15s',
|
||||
position: 'relative',
|
||||
@@ -58,7 +58,6 @@ export const StyledListItem = styled.div<{
|
||||
width: '296px',
|
||||
height: '32px',
|
||||
marginTop: '12px',
|
||||
fontSize: theme.font.sm,
|
||||
color: active ? theme.colors.primaryColor : theme.colors.popoverColor,
|
||||
paddingLeft: '12px',
|
||||
borderRadius: '5px',
|
||||
@@ -122,9 +121,8 @@ export const StyledSubListItem = styled.button<{
|
||||
width: '296px',
|
||||
height: '32px',
|
||||
marginTop: '4px',
|
||||
fontSize: theme.font.sm,
|
||||
color: disable
|
||||
? theme.colors.iconColor
|
||||
? theme.colors.disableColor
|
||||
: active
|
||||
? theme.colors.primaryColor
|
||||
: theme.colors.popoverColor,
|
||||
|
||||
@@ -38,7 +38,7 @@ export const useEnsureWorkspace = () => {
|
||||
// }
|
||||
const workspaceId =
|
||||
(router.query.workspaceId as string) || workspaceList[0]?.id;
|
||||
loadWorkspace(workspaceId).finally(() => {
|
||||
loadWorkspace.current(workspaceId).finally(() => {
|
||||
setWorkspaceLoaded(true);
|
||||
setActiveWorkspaceId(activeWorkspaceId);
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { uuidv4, Workspace } from '@blocksuite/store';
|
||||
import { QueryContent } from '@blocksuite/store/dist/workspace/search';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { EditorContainer } from '@blocksuite/editor';
|
||||
@@ -20,7 +20,10 @@ export type EditorHandlers = {
|
||||
toggleDeletePage: (pageId: string) => Promise<boolean>;
|
||||
toggleFavoritePage: (pageId: string) => Promise<boolean>;
|
||||
permanentlyDeletePage: (pageId: string) => void;
|
||||
search: (query: QueryContent) => Map<string, string | undefined>;
|
||||
search: (
|
||||
query: QueryContent,
|
||||
workspace?: Workspace
|
||||
) => Map<string, string | undefined>;
|
||||
// changeEditorMode: (pageId: string) => void;
|
||||
changePageMode: (
|
||||
pageId: string,
|
||||
@@ -83,9 +86,16 @@ export const usePageHelper = (): EditorHandlers => {
|
||||
});
|
||||
return trash;
|
||||
},
|
||||
search: (query: QueryContent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return currentWorkspace!.blocksuiteWorkspace!.search(query);
|
||||
search: (query: QueryContent, workspace?: Workspace) => {
|
||||
if (workspace) {
|
||||
return workspace.search(query);
|
||||
}
|
||||
if (currentWorkspace) {
|
||||
if (currentWorkspace.blocksuiteWorkspace) {
|
||||
return currentWorkspace.blocksuiteWorkspace.search(query);
|
||||
}
|
||||
}
|
||||
return new Map();
|
||||
},
|
||||
changePageMode: async (pageId, mode) => {
|
||||
const pageMeta = getPageMeta(currentWorkspace, pageId);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { useConfirm } from '@/providers/ConfirmProvider';
|
||||
import { toast } from '@/ui/toast';
|
||||
import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import router from 'next/router';
|
||||
|
||||
export const useWorkspaceHelper = () => {
|
||||
const { confirm } = useConfirm();
|
||||
const { dataCenter, currentWorkspace, user, login } = useAppState();
|
||||
const { dataCenter, currentWorkspace } = useAppState();
|
||||
const createWorkspace = async (name: string) => {
|
||||
const workspaceInfo = await dataCenter.createWorkspace({
|
||||
name: name,
|
||||
@@ -31,33 +27,11 @@ export const useWorkspaceHelper = () => {
|
||||
await dataCenter.updateWorkspaceMeta({ name }, workspace);
|
||||
}
|
||||
if (avatarBlob) {
|
||||
// const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
||||
// await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
||||
const blobId = await dataCenter.setBlob(workspace, avatarBlob);
|
||||
await dataCenter.updateWorkspaceMeta({ avatar: blobId }, workspace);
|
||||
}
|
||||
};
|
||||
|
||||
const enableWorkspace = async () => {
|
||||
confirm({
|
||||
title: 'Enable AFFiNE Cloud?',
|
||||
content: `If enabled, the data in this workspace will be backed up and synchronized via AFFiNE Cloud.`,
|
||||
confirmText: user ? 'Enable' : 'Sign in and Enable',
|
||||
cancelText: 'Skip',
|
||||
confirmType: 'primary',
|
||||
buttonDirection: 'column',
|
||||
}).then(async confirm => {
|
||||
if (confirm && currentWorkspace) {
|
||||
if (!user) {
|
||||
await login();
|
||||
}
|
||||
const workspace = await dataCenter.enableWorkspaceCloud(
|
||||
currentWorkspace
|
||||
);
|
||||
workspace && router.push(`/workspace/${workspace.id}/setting`);
|
||||
toast('Enabled success');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const deleteWorkSpace = async () => {
|
||||
currentWorkspace && (await dataCenter.deleteWorkspace(currentWorkspace.id));
|
||||
};
|
||||
@@ -77,7 +51,6 @@ export const useWorkspaceHelper = () => {
|
||||
createWorkspace,
|
||||
publishWorkspace,
|
||||
updateWorkspace,
|
||||
enableWorkspace,
|
||||
deleteWorkSpace,
|
||||
leaveWorkSpace,
|
||||
acceptInvite,
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export const AlreadyJoined = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
You are already a member of <code>Workspace name</code>
|
||||
</p>
|
||||
<button>Open Workspace</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
export const Confirm = () => {
|
||||
return (
|
||||
<div>
|
||||
<p>Inviter name</p>
|
||||
<p>
|
||||
invite you to join in <code>Workspace name</code>
|
||||
</p>
|
||||
<button>Join</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
export const LinkExpired = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>The current invitation link has expired.</h1>
|
||||
<a href={location.origin}>Back to home</a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
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 (
|
||||
<div>
|
||||
<h1>Confirm Invitation</h1>
|
||||
{Component ? <Component /> : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -67,15 +67,20 @@ const App = ({ Component, pageProps }: AppPropsWithLayout) => {
|
||||
};
|
||||
|
||||
const AppDefender = ({ children }: PropsWithChildren) => {
|
||||
const { synced } = useAppState();
|
||||
const router = useRouter();
|
||||
const { synced } = useAppState();
|
||||
|
||||
useEffect(() => {
|
||||
if (router.pathname === '/') {
|
||||
if (router.asPath === '/') {
|
||||
router.replace('/workspace');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
// if you visit /404, you will see the children directly
|
||||
if (router.route === '/404') {
|
||||
return <div>{children}</div>;
|
||||
}
|
||||
|
||||
return <div>{synced ? children : <PageLoading />}</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -36,7 +36,6 @@ const Affine = () => {
|
||||
<div>hi</div>
|
||||
</Modal>
|
||||
<Loading />
|
||||
|
||||
<Button
|
||||
icon={<FavouritedIcon />}
|
||||
onClick={() => {
|
||||
@@ -48,13 +47,15 @@ const Affine = () => {
|
||||
<Button icon={<FavouritedIcon />} type={'primary'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'light'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'warning'}>
|
||||
click me!
|
||||
</Button>
|
||||
<Button icon={<FavouritedIcon />} type={'danger'}>
|
||||
click me!
|
||||
</Button>
|
||||
|
||||
<Button icon={<FavouritedIcon />}></Button>
|
||||
<Button icon={<FavouritedIcon />} shape="round"></Button>
|
||||
<Button loading={true}></Button>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import type { NextPage } from 'next';
|
||||
import { ConfirmInvitation } from '../pages-content/ConfirmInvitationPage';
|
||||
|
||||
const ConfirmInvitationPage: NextPage = () => {
|
||||
return <ConfirmInvitation />;
|
||||
};
|
||||
|
||||
export default ConfirmInvitationPage;
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useWorkspaceHelper } from '@/hooks/use-workspace-helper';
|
||||
import { styled } from '@/styles';
|
||||
import { Empty } from '@/ui/empty';
|
||||
// import { Avatar } from '@mui/material';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
|
||||
// const User = ({ name, avatar }: { name: string; avatar?: string }) => {
|
||||
// return (
|
||||
@@ -19,19 +19,29 @@ import { useEffect, useState } from 'react';
|
||||
// };
|
||||
|
||||
export default function DevPage() {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
const [successInvited, setSuccessInvited] = useState<boolean>(false);
|
||||
const { acceptInvite } = useWorkspaceHelper();
|
||||
useEffect(() => {
|
||||
router.query.invite_code &&
|
||||
acceptInvite(router.query.invite_code as string).then(data => {
|
||||
if (data && data.accepted) {
|
||||
setSuccessInvited(true);
|
||||
}
|
||||
});
|
||||
if (router.query.invite_code) {
|
||||
acceptInvite(router.query.invite_code as string)
|
||||
.then(data => {
|
||||
if (data && data.accepted) {
|
||||
setSuccessInvited(true);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [router, acceptInvite]);
|
||||
|
||||
return (
|
||||
return loading ? (
|
||||
<PageLoading />
|
||||
) : (
|
||||
<Invited>
|
||||
<div>
|
||||
<Empty width={310} height={310}></Empty>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import type { NextPageWithLayout } from '../..//_app';
|
||||
import { styled } from '@/styles';
|
||||
import { displayFlex, styled } from '@/styles';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { useRouter } from 'next/router';
|
||||
import { Page as PageStore, Workspace } from '@blocksuite/store';
|
||||
import { PageLoading } from '@/components/loading';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { IconButton } from '@/ui/button';
|
||||
import NextLink from 'next/link';
|
||||
import { PaperIcon, SearchIcon } from '@blocksuite/icons';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
|
||||
const DynamicBlocksuite = dynamic(() => import('@/components/editor'), {
|
||||
ssr: false,
|
||||
@@ -16,12 +22,16 @@ const Page: NextPageWithLayout = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [pageTitle, setPageTitle] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
if (data && data.blocksuiteWorkspace) {
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
if (data.blocksuiteWorkspace) {
|
||||
setWorkspace(data.blocksuiteWorkspace);
|
||||
if (
|
||||
router.query.pageId &&
|
||||
@@ -32,6 +42,7 @@ const Page: NextPageWithLayout = () => {
|
||||
const page = data.blocksuiteWorkspace.getPage(
|
||||
router.query.pageId as string
|
||||
);
|
||||
page && setPageTitle(page.meta.title);
|
||||
page && setPage(page);
|
||||
} else {
|
||||
router.push('/404');
|
||||
@@ -46,6 +57,30 @@ const Page: NextPageWithLayout = () => {
|
||||
<>
|
||||
{!loaded && <PageLoading />}
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}/${router.query.pageId}`}
|
||||
>
|
||||
<PaperIcon fontSize={24} />
|
||||
<span>{pageTitle ? pageTitle : 'Untitled'}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
|
||||
{workspace && page && (
|
||||
<DynamicBlocksuite
|
||||
page={page}
|
||||
@@ -69,9 +104,43 @@ export default Page;
|
||||
|
||||
export const PageContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
height: 'calc(100vh)',
|
||||
padding: '78px 72px',
|
||||
height: '100vh',
|
||||
overflowY: 'auto',
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const NavContainer = styled.div(({ theme }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
padding: '0 12px',
|
||||
height: '60px',
|
||||
...displayFlex('start', 'center'),
|
||||
backgroundColor: theme.colors.pageBackground,
|
||||
};
|
||||
});
|
||||
export const StyledBreadcrumbs = styled(NextLink)(({ theme }) => {
|
||||
return {
|
||||
flex: 1,
|
||||
...displayFlex('center', 'center'),
|
||||
paddingLeft: '12px',
|
||||
span: {
|
||||
padding: '0 12px',
|
||||
fontSize: theme.font.base,
|
||||
lineHeight: theme.font.lineHeight,
|
||||
},
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
transition: 'all .15s',
|
||||
':visited': {
|
||||
color: theme.colors.popoverColor,
|
||||
':hover': { color: theme.colors.primaryColor },
|
||||
},
|
||||
};
|
||||
});
|
||||
export const SearchButton = styled(IconButton)(({ theme }) => {
|
||||
return {
|
||||
color: theme.colors.iconColor,
|
||||
fontSize: '24px',
|
||||
marginLeft: 'auto',
|
||||
padding: '0 24px',
|
||||
};
|
||||
});
|
||||
|
||||
@@ -2,16 +2,28 @@ import { PageList } from '@/components/page-list';
|
||||
import { ReactElement, useEffect, useState } from 'react';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
import { useRouter } from 'next/router';
|
||||
import { PageContainer } from './[pageId]';
|
||||
import {
|
||||
PageContainer,
|
||||
NavContainer,
|
||||
StyledBreadcrumbs,
|
||||
SearchButton,
|
||||
} from './[pageId]';
|
||||
import { Breadcrumbs } from '@/ui/breadcrumbs';
|
||||
import { WorkspaceUnitAvatar } from '@/components/workspace-avatar';
|
||||
import { SearchIcon } from '@blocksuite/icons';
|
||||
import { useModal } from '@/providers/GlobalModalProvider';
|
||||
const All = () => {
|
||||
const { dataCenter } = useAppState();
|
||||
const router = useRouter();
|
||||
const [pageList, setPageList] = useState<PageMeta[]>([]);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const { triggerQuickSearchModal } = useModal();
|
||||
useEffect(() => {
|
||||
dataCenter
|
||||
.loadPublicWorkspace(router.query.workspaceId as string)
|
||||
.then(data => {
|
||||
setPageList(data.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]);
|
||||
setWorkspaceName(data.blocksuiteWorkspace?.meta.name as string);
|
||||
})
|
||||
.catch(() => {
|
||||
router.push('/404');
|
||||
@@ -20,6 +32,23 @@ const All = () => {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavContainer>
|
||||
<Breadcrumbs>
|
||||
<StyledBreadcrumbs
|
||||
href={`/public-workspace/${router.query.workspaceId}`}
|
||||
>
|
||||
<WorkspaceUnitAvatar size={24} name={workspaceName} />
|
||||
<span>{workspaceName}</span>
|
||||
</StyledBreadcrumbs>
|
||||
</Breadcrumbs>
|
||||
<SearchButton
|
||||
onClick={() => {
|
||||
triggerQuickSearchModal();
|
||||
}}
|
||||
>
|
||||
<SearchIcon />
|
||||
</SearchButton>
|
||||
</NavContainer>
|
||||
<PageList
|
||||
pageList={pageList.filter(p => !p.trash)}
|
||||
showFavoriteTag={false}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
debug-menu {
|
||||
display: none !important;
|
||||
}
|
||||
.affine-block-children-container.edgeless {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.affine-default-page-block-title-container {
|
||||
margin-top: 78px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.affine-editor-container {
|
||||
background: transparent !important;
|
||||
|
||||
.affine-default-page-block-container {
|
||||
width: 686px !important;
|
||||
}
|
||||
|
||||
@@ -4,9 +4,11 @@ import { PageListHeader } from '@/components/header';
|
||||
import { ReactElement } from 'react';
|
||||
import WorkspaceLayout from '@/components/workspace-layout';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
import { PageMeta, useAppState } from '@/providers/app-state-provider';
|
||||
const All = () => {
|
||||
const { pageList } = useAppState();
|
||||
const { currentWorkspace } = useAppState();
|
||||
const pageList = (currentWorkspace?.blocksuiteWorkspace?.meta.pageMetas ||
|
||||
[]) as PageMeta[];
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
GeneralPage,
|
||||
MembersPage,
|
||||
PublishPage,
|
||||
ExportPage,
|
||||
SyncPage,
|
||||
ExportPage,
|
||||
} from '@/components/workspace-setting';
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import { useAppState } from '@/providers/app-state-provider';
|
||||
@@ -20,41 +20,37 @@ import { WorkspaceUnit } from '@affine/datacenter';
|
||||
import { useTranslation } from '@affine/i18n';
|
||||
import { PageListHeader } from '@/components/header';
|
||||
|
||||
type TabNames = 'General' | 'Sync' | 'Collaboration' | 'Publish' | 'Export';
|
||||
|
||||
const tabMap: {
|
||||
name: TabNames;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: 'General',
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Sync',
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Collaboration',
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: 'Publish',
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Export',
|
||||
panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
},
|
||||
];
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const useTabMap = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace, isOwner } = useAppState();
|
||||
|
||||
const [activeTab, setActiveTab] = useState<TabNames>(tabMap[0].name);
|
||||
const handleTabChange = (tab: TabNames) => {
|
||||
const { isOwner } = useAppState();
|
||||
const tabMap: {
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = [
|
||||
{
|
||||
name: t('General'),
|
||||
panelRender: workspace => <GeneralPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Sync'),
|
||||
panelRender: workspace => <SyncPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Collaboration'),
|
||||
panelRender: workspace => <MembersPage workspace={workspace} />,
|
||||
},
|
||||
{
|
||||
name: t('Publish'),
|
||||
panelRender: workspace => <PublishPage workspace={workspace} />,
|
||||
},
|
||||
// TODO: next version will finish this feature
|
||||
{
|
||||
name: t('Export'),
|
||||
panelRender: workspace => <ExportPage workspace={workspace} />,
|
||||
},
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState<string>(tabMap[0].name);
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
};
|
||||
|
||||
@@ -62,7 +58,7 @@ const WorkspaceSetting = () => {
|
||||
tab => tab.name === activeTab
|
||||
)?.panelRender;
|
||||
let tableArr: {
|
||||
name: TabNames;
|
||||
name: string;
|
||||
panelRender: (workspace: WorkspaceUnit) => ReactNode;
|
||||
}[] = tabMap;
|
||||
if (!isOwner) {
|
||||
@@ -73,6 +69,14 @@ const WorkspaceSetting = () => {
|
||||
},
|
||||
];
|
||||
}
|
||||
return { activeTabPanelRender, tableArr, handleTabChange, activeTab };
|
||||
};
|
||||
|
||||
const WorkspaceSetting = () => {
|
||||
const { t } = useTranslation();
|
||||
const { currentWorkspace } = useAppState();
|
||||
const { activeTabPanelRender, tableArr, handleTabChange, activeTab } =
|
||||
useTabMap();
|
||||
return (
|
||||
<>
|
||||
<StyledSettingContainer>
|
||||
|
||||
@@ -10,16 +10,21 @@ import {
|
||||
import { createDefaultWorkspace } from './utils';
|
||||
import { User } from '@affine/datacenter';
|
||||
|
||||
export interface Disposable {
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
type AppStateContextProps = PropsWithChildren<Record<string, unknown>>;
|
||||
|
||||
export const AppState = createContext<AppStateContext>({} as AppStateContext);
|
||||
|
||||
export const useAppState = () => useContext(AppState);
|
||||
|
||||
export const AppStateProvider = ({
|
||||
children,
|
||||
}: PropsWithChildren<AppStateContextProps>) => {
|
||||
const [appState, setAppState] = useState<AppStateValue>({} as AppStateValue);
|
||||
const [blobState, setBlobState] = useState(false);
|
||||
const [userInfo, setUser] = useState<User | null>({} as User);
|
||||
useEffect(() => {
|
||||
const initState = async () => {
|
||||
const dataCenter = await getDataCenter();
|
||||
@@ -27,10 +32,9 @@ export const AppStateProvider = ({
|
||||
if (dataCenter.workspaces.length === 0) {
|
||||
await createDefaultWorkspace(dataCenter);
|
||||
}
|
||||
|
||||
setUser((await dataCenter.getUserInfo()) || null);
|
||||
setAppState({
|
||||
dataCenter,
|
||||
user: (await dataCenter.getUserInfo()) || null,
|
||||
workspaceList: dataCenter.workspaces,
|
||||
currentWorkspace: null,
|
||||
pageList: [],
|
||||
@@ -90,15 +94,17 @@ export const AppStateProvider = ({
|
||||
});
|
||||
};
|
||||
|
||||
const loadWorkspace = useRef<AppStateFunction['loadWorkspace']>();
|
||||
const loadWorkspace: AppStateFunction['loadWorkspace'] =
|
||||
useRef() as AppStateFunction['loadWorkspace'];
|
||||
loadWorkspace.current = async (workspaceId: string) => {
|
||||
const { dataCenter, workspaceList, currentWorkspace, user } = appState;
|
||||
const { dataCenter, workspaceList, currentWorkspace } = appState;
|
||||
if (!workspaceList.find(v => v.id.toString() === workspaceId)) {
|
||||
return null;
|
||||
}
|
||||
if (workspaceId === currentWorkspace?.id) {
|
||||
return currentWorkspace;
|
||||
}
|
||||
|
||||
const workspace = (await dataCenter.loadWorkspace(workspaceId)) ?? null;
|
||||
let isOwner;
|
||||
if (workspace?.provider === 'local') {
|
||||
@@ -106,8 +112,9 @@ export const AppStateProvider = ({
|
||||
isOwner = true;
|
||||
} else {
|
||||
// We must ensure workspace.owner exists, then ensure id same.
|
||||
isOwner = workspace?.owner && user?.id === workspace.owner.id;
|
||||
isOwner = workspace?.owner && userInfo?.id === workspace.owner.id;
|
||||
}
|
||||
|
||||
const pageList =
|
||||
(workspace?.blocksuiteWorkspace?.meta.pageMetas as PageMeta[]) ?? [];
|
||||
setAppState({
|
||||
@@ -122,6 +129,26 @@ export const AppStateProvider = ({
|
||||
return workspace;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let syncChangeDisposable: Disposable | undefined;
|
||||
const currentWorkspace = appState.currentWorkspace;
|
||||
if (!currentWorkspace) {
|
||||
return;
|
||||
}
|
||||
const getBlobStorage = async () => {
|
||||
const blobStorage = await currentWorkspace?.blocksuiteWorkspace?.blobs;
|
||||
syncChangeDisposable = blobStorage?.signals.onBlobSyncStateChange.on(
|
||||
() => {
|
||||
setBlobState(blobStorage?.uploading);
|
||||
}
|
||||
);
|
||||
};
|
||||
getBlobStorage();
|
||||
return () => {
|
||||
syncChangeDisposable?.dispose();
|
||||
};
|
||||
}, [appState.currentWorkspace]);
|
||||
|
||||
const setEditor: AppStateFunction['setEditor'] =
|
||||
useRef() as AppStateFunction['setEditor'];
|
||||
setEditor.current = editor => {
|
||||
@@ -139,20 +166,13 @@ export const AppStateProvider = ({
|
||||
if (!user) {
|
||||
throw new Error('User info not found');
|
||||
}
|
||||
setAppState({
|
||||
...appState,
|
||||
user,
|
||||
});
|
||||
setUser(user);
|
||||
return user;
|
||||
};
|
||||
|
||||
const logout = async () => {
|
||||
const { dataCenter } = appState;
|
||||
await dataCenter.logout();
|
||||
setAppState({
|
||||
...appState,
|
||||
user: null,
|
||||
});
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -161,9 +181,11 @@ export const AppStateProvider = ({
|
||||
...appState,
|
||||
setEditor,
|
||||
loadPage: loadPage.current,
|
||||
loadWorkspace: loadWorkspace.current,
|
||||
loadWorkspace: loadWorkspace,
|
||||
login,
|
||||
logout,
|
||||
blobDataSynced: blobState,
|
||||
user: userInfo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -16,7 +16,7 @@ export interface PageMeta extends StorePageMeta {
|
||||
|
||||
export type AppStateValue = {
|
||||
dataCenter: DataCenter;
|
||||
user: User | null;
|
||||
user?: User | null;
|
||||
workspaceList: WorkspaceUnit[];
|
||||
currentWorkspace: WorkspaceUnit | null;
|
||||
pageList: PageMeta[];
|
||||
@@ -24,12 +24,15 @@ export type AppStateValue = {
|
||||
editor?: EditorContainer | null;
|
||||
synced: boolean;
|
||||
isOwner?: boolean;
|
||||
blobDataSynced?: boolean;
|
||||
};
|
||||
|
||||
export type AppStateFunction = {
|
||||
setEditor: MutableRefObject<(page: EditorContainer) => void>;
|
||||
|
||||
loadWorkspace: (workspaceId: string) => Promise<WorkspaceUnit | null>;
|
||||
loadWorkspace: MutableRefObject<
|
||||
(workspaceId: string) => Promise<WorkspaceUnit | null>
|
||||
>;
|
||||
loadPage: (pageId: string) => void;
|
||||
|
||||
login: () => Promise<User>;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DataCenter } from '@affine/datacenter';
|
||||
|
||||
const DEFAULT_WORKSPACE_NAME = 'affine';
|
||||
const DEFAULT_WORKSPACE_NAME = 'AFFiNE Test';
|
||||
|
||||
export const createDefaultWorkspace = async (dataCenter: DataCenter) => {
|
||||
return dataCenter.createWorkspace({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user