mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-06 08:50:50 +08:00
Merge pull request #225 from toeverything/develop
release: v0.0.1-alpha.0.4.1
This commit is contained in:
+112
-3
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"projectName": "toeverything",
|
"projectName": "AFFiNE",
|
||||||
"projectOwner": "toeverything",
|
"projectOwner": "toeverything",
|
||||||
"repoType": "github",
|
"repoType": "github",
|
||||||
"repoHost": "https://github.com",
|
"repoHost": "https://github.com",
|
||||||
"files": [
|
"files": [
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"imageSize": 100,
|
"imageSize": 50,
|
||||||
"commit": false,
|
"commit": false,
|
||||||
"commitConvention": "angular",
|
"commitConvention": "angular",
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
@@ -115,11 +115,120 @@
|
|||||||
"login": "uptonking",
|
"login": "uptonking",
|
||||||
"name": "Jin Yao",
|
"name": "Jin Yao",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/11391549?v=4",
|
||||||
"profile": "https://github.com/uptonking?tab=repositories&type=source",
|
"profile": "https://github.com/uptonking",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code",
|
||||||
"doc"
|
"doc"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "HeJiachen-PM",
|
||||||
|
"name": "HeJiachen-PM",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
|
||||||
|
"profile": "https://github.com/HeJiachen-PM",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Yipei-Operation",
|
||||||
|
"name": "Yipei Wei",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/79373028?v=4",
|
||||||
|
"profile": "https://github.com/Yipei-Operation",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "fanjing22",
|
||||||
|
"name": "fanjing22",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/109729699?v=4",
|
||||||
|
"profile": "https://github.com/fanjing22",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Svaney-ssman",
|
||||||
|
"name": "Svaney",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/110808979?v=4",
|
||||||
|
"profile": "https://github.com/Svaney-ssman",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "xell",
|
||||||
|
"name": "Guozhu Liu",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/132558?v=4",
|
||||||
|
"profile": "http://xell.me/",
|
||||||
|
"contributions": [
|
||||||
|
"design"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "fyZheng07",
|
||||||
|
"name": "fyZheng07",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/63830919?v=4",
|
||||||
|
"profile": "https://github.com/fyZheng07",
|
||||||
|
"contributions": [
|
||||||
|
"eventOrganizing",
|
||||||
|
"userTesting"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "CJSS",
|
||||||
|
"name": "CJSS",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/4605025?v=4",
|
||||||
|
"profile": "https://github.com/CJSS",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "CarlosZoft",
|
||||||
|
"name": "Carlos Rafael ",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/62192072?v=4",
|
||||||
|
"profile": "https://github.com/clean-software",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "caleboleary",
|
||||||
|
"name": "Caleb OLeary",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/12816579?v=4",
|
||||||
|
"profile": "https://github.com/caleboleary",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "JimmFly",
|
||||||
|
"name": "JimmFly",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/102217452?v=4",
|
||||||
|
"profile": "https://github.com/JimmFly",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "westongraham",
|
||||||
|
"name": "Weston Graham",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/89493023?v=4",
|
||||||
|
"profile": "https://github.com/westongraham",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "pointmax",
|
||||||
|
"name": "pointmax",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/49361135?v=4",
|
||||||
|
"profile": "https://github.com/pointmax",
|
||||||
|
"contributions": [
|
||||||
|
"doc"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -4,6 +4,7 @@
|
|||||||
"editor.formatOnSaveMode": "file",
|
"editor.formatOnSaveMode": "file",
|
||||||
"prettier.prettierPath": "./node_modules/prettier",
|
"prettier.prettierPath": "./node_modules/prettier",
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
|
"AUTOINCREMENT",
|
||||||
"Backlinks",
|
"Backlinks",
|
||||||
"blockdb",
|
"blockdb",
|
||||||
"booktitle",
|
"booktitle",
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to creating a positive environment include:
|
||||||
|
|
||||||
|
- Using welcoming and inclusive language
|
||||||
|
- Being respectful of differing viewpoints and experiences
|
||||||
|
- Gracefully accepting constructive criticism
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
- Showing empathy towards other community members
|
||||||
|
|
||||||
|
Examples of unacceptable behavior by participants include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||||
|
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||||
|
|
||||||
|
## Our Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||||
|
|
||||||
|
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||||
|
|
||||||
|
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at <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>
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
# Welcome to ourcontributing guide <!-- omit in toc -->
|
||||||
|
|
||||||
|
Thank you for investing your time in contributing to our project! Any contribution you make will be reflected on our GitHub :sparkles:.
|
||||||
|
|
||||||
|
Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable.
|
||||||
|
|
||||||
|
In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR.
|
||||||
|
|
||||||
|
Use the table of contents icon on the top left corner of this document to get to a specific section of this guide quickly.
|
||||||
|
|
||||||
|
## New contributor guide
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
- [Finding ways to contribute to open source on GitHub](https://docs.github.com/en/get-started/exploring-projects-on-github/finding-ways-to-contribute-to-open-source-on-github)
|
||||||
|
- [Set up Git](https://docs.github.com/en/get-started/quickstart/set-up-git)
|
||||||
|
- [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow)
|
||||||
|
- [Collaborating with pull requests](https://docs.github.com/en/github/collaborating-with-pull-requests)
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
To navigate our codebase with confidence, see [the introduction to working in the docs repository](/contributing/working-in-docs-repository.md) :confetti_ball:. For more information on how we write our markdown files, see [the GitHub Markdown reference](contributing/content-markup-reference.md).
|
||||||
|
|
||||||
|
Check to see what [types of contributions](/contributing/types-of-contributions.md) we accept before making changes. Some of them don't even require writing a single line of code :sparkles:.
|
||||||
|
|
||||||
|
### Issues
|
||||||
|
|
||||||
|
#### Create a new issue
|
||||||
|
|
||||||
|
If you spot a problem, [search if an issue already exists](https://docs.github.com/en/github/searching-for-information-on-github/searching-on-github/searching-issues-and-pull-requests#search-by-the-title-body-or-comments). If a related issue doesn't exist, you can open a new issue using a relevant [issue form](https://github.com/toeverything/AFFiNE/issues/new/choose).
|
||||||
|
|
||||||
|
#### Solve an issue
|
||||||
|
|
||||||
|
Scan through our [existing issues](https://github.com/toeverything/AFFiNE/issues) to find one that interests you. You can narrow down the search using `labels` as filters. See [Labels](/contributing/how-to-use-labels.md) for more information. As a general rule, we don’t assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
|
||||||
|
|
||||||
|
### Make Changes
|
||||||
|
|
||||||
|
#### Make changes in the UI
|
||||||
|
|
||||||
|
Click **Make a contribution** at the bottom of any docs page to make small changes such as a typo, sentence fix, or a broken link. This takes you to the `.md` file where you can make your changes and [create a pull request](#pull-request) for a review.
|
||||||
|
|
||||||
|
#### Make changes in a codespace
|
||||||
|
|
||||||
|
For more information about using a codespace for working on GitHub documentation, see "[Working in a codespace](https://github.com/github/docs/blob/main/contributing/codespace.md)."
|
||||||
|
|
||||||
|
#### Make changes locally
|
||||||
|
|
||||||
|
1. [Install Git LFS](https://docs.github.com/en/github/managing-large-files/versioning-large-files/installing-git-large-file-storage).
|
||||||
|
|
||||||
|
2. Fork the repository.
|
||||||
|
|
||||||
|
- Using GitHub Desktop:
|
||||||
|
|
||||||
|
- [Getting started with GitHub Desktop](https://docs.github.com/en/desktop/installing-and-configuring-github-desktop/getting-started-with-github-desktop) will guide you through setting up Desktop.
|
||||||
|
- Once Desktop is set up, you can use it to [fork the repo](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/cloning-and-forking-repositories-from-github-desktop)!
|
||||||
|
|
||||||
|
- Using the command line:
|
||||||
|
- [Fork the repo](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo#fork-an-example-repository) so that you can make your changes without affecting the original project until you're ready to merge them.
|
||||||
|
|
||||||
|
3. Install or update to **Node.js v16**. For more information, see [the development guide](contributing/development.md).
|
||||||
|
|
||||||
|
4. Create a working branch and start with your changes!
|
||||||
|
|
||||||
|
### Commit your update
|
||||||
|
|
||||||
|
Commit the changes once you are happy with them.
|
||||||
|
|
||||||
|
Once your changes are ready, don't forget to self-review to speed up the review process:zap:.
|
||||||
|
|
||||||
|
### Pull Request
|
||||||
|
|
||||||
|
When you're finished with the changes, create a pull request, also known as a PR.
|
||||||
|
|
||||||
|
- Fill the "Ready for review" template so that we can review your PR. This template helps reviewers understand your changes as well as the purpose of your pull request.
|
||||||
|
- Don't forget to [link PR to issue](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) if you are solving one.
|
||||||
|
- Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge.
|
||||||
|
Once you submit your PR, a Docs team member will review your proposal. We may ask questions or request for additional information.
|
||||||
|
- We may ask for changes to be made before a PR can be merged, either using [suggested changes](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request) or pull request comments. You can apply suggested changes directly through the UI. You can make any other changes in your fork, then commit them to your branch.
|
||||||
|
- As you update your PR and apply changes, mark each conversation as [resolved](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/commenting-on-a-pull-request#resolving-conversations).
|
||||||
|
- If you run into any merge issues, checkout this [git tutorial](https://github.com/skills/resolve-merge-conflicts) to help you resolve merge conflicts and other issues.
|
||||||
|
|
||||||
|
### Your PR is merged!
|
||||||
|
|
||||||
|
Congratulations :tada::tada: The AFFiNE team thanks you :sparkles:.
|
||||||
|
|
||||||
|
Once your PR is merged, your contributions will be publicly visible on the our GitHub.
|
||||||
|
|
||||||
|
Now that you are part of the AFFiNE community, see how else you can join and help over at [Gitbook](https://docs.affine.pro/affine/)
|
||||||
@@ -18,10 +18,12 @@ 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:START - Do not remove or modify this section -->
|
||||||
|
|
||||||
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square
|
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square
|
||||||
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
||||||
|
|
||||||
|
[](https://affine.pro)
|
||||||
|
[](https://github.com/toeverything/AFFiNE)
|
||||||
[![All Contributors][all-contributors-badge]](#contributors)
|
[![All Contributors][all-contributors-badge]](#contributors)
|
||||||
[](https://www.typescriptlang.org/)
|
[](https://www.typescriptlang.org/)
|
||||||
[](https://www.typescriptlang.org/)
|
[](https://www.typescriptlang.org/)
|
||||||
@@ -46,8 +48,24 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
|||||||
|
|
||||||
# How to use
|
# How to use
|
||||||
|
|
||||||
If you have experience in front-end development, please [refer to here](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine); if you want to experience our latest version, please wait a moment, we will launch a web version in the near future.
|
<h3 align="center">
|
||||||
And, thanks to Lee who [made a desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out.
|
🥳🥳🥳 Our web live demo is ready! 🥳🥳🥳
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<h3 align="center">
|
||||||
|
Start to play with <a href="https://affine.pro"><b>AFFiNE web version</b></a> on our landing page:
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p align="center"><a href="https://affine.pro">
|
||||||
|
<img
|
||||||
|
src="https://user-images.githubusercontent.com/79301703/184340907-5aaa4e6e-7d37-4523-a06c-7d630e7864d1.jpeg"
|
||||||
|
width="600px"
|
||||||
|
alt="check_live_demo"
|
||||||
|
/>
|
||||||
|
</a></p>
|
||||||
|
|
||||||
|
If you have experience in front-end development, you may wish to refer to our [documentation](https://docs.affine.pro/affine/basic-documentation/contribute-to-affine) to learn more about deploying your own version or contributing further to development.
|
||||||
|
Also, thanks to Lee who has made a [desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out.
|
||||||
Please notice that AFFiNE is still under Alpha stage and is not ready for production use.
|
Please notice that AFFiNE is still under Alpha stage and is not ready for production use.
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
@@ -85,13 +103,13 @@ Please notice that AFFiNE is still under Alpha stage and is not ready for produc
|
|||||||
|
|
||||||
## Create your story
|
## Create your story
|
||||||
|
|
||||||
We want your data always to be yours, and we don't want to make any sacrifice to your accessibility. Your data is always local-stored first, yet we support real-time collaboration on a peer-to-peer basis. We don't think "privacy-first" is a good excuse for not supporting modern web features.
|
We want your data always to be yours, without any sacrifice to your accessibility. Your data is always stored local first, yet we support real-time collaboration on a peer-to-peer basis. We don't think "privacy-first" is a good excuse for not supporting modern web features.
|
||||||
Collaboration isn't only necessary for teams -- you may take and insert pics on your phone, then edit them on your desktop, and share them with your collaborators.
|
And when it comes to collaboration, these features are not just necessarily for teams -- you can take and insert pictures on your phone, edit them from your desktop, and then share them with your collaborators.
|
||||||
Affine is fully built with web technologies so that consistency and accessibility are always guaranteed on Mac, Windows and Linux. The local file system support will be available when version 0.0.1beta is released.
|
Affine is fully built with web technologies to ensure consistency and accessibility on Mac, Windows and Linux. The local file system support will be available when version 0.0.1beta is released.
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
|
|
||||||
AFFiNE is not yet ready for production use. To install, you may check how to build or deploy the AFFiNE in [quick-start](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine/quick-start). For the full documentation, please view it [here](https://affine.gitbook.io/affine/).
|
AFFiNE is not yet ready for production use. For installation, you may check how to build or deploy AFFiNE from our [quick-start](https://docs.affine.pro/affine/basic-documentation/contribute-to-affine/quick-start) guide. Alternatively, you can view our [full documentation](https://docs.affine.pro/affine/).
|
||||||
|
|
||||||
## Getting Started with development
|
## Getting Started with development
|
||||||
|
|
||||||
@@ -107,28 +125,28 @@ Get our latest [release notes](https://github.com/toeverything/AFFiNE/wiki) from
|
|||||||
|
|
||||||
# Feature requests
|
# Feature requests
|
||||||
|
|
||||||
Please go to [Feature request](https://github.com/toeverything/AFFiNE/issues).
|
Please go to [feature requests](https://github.com/toeverything/AFFiNE/issues).
|
||||||
|
|
||||||
# FAQ
|
# FAQ
|
||||||
|
|
||||||
Get quick help on [Telegram](https://t.me/affineworkos) and [Discord](https://discord.gg/yz6tGVsf5p) along with other developers and contributors.
|
Get quick help on [Telegram](https://t.me/affineworkos) or [Discord](https://discord.gg/yz6tGVsf5p) and join our community of developers and contributors.
|
||||||
|
|
||||||
Latest news and technology sharing on [Twitter](https://twitter.com/AffineOfficial), [Medium](https://medium.com/@affineworkos) and [AFFiNE Blog](https://blog.affine.pro/).
|
Our latest news can be found on [Twitter](https://twitter.com/AffineOfficial), [Medium](https://medium.com/@affineworkos) and the [AFFiNE Blog](https://blog.affine.pro/).
|
||||||
|
|
||||||
# The Philosophy of AFFiNE
|
# The Philosophy of AFFiNE
|
||||||
|
|
||||||
Timothy Berners-Lee once taught us about the idea of the semantic web, where all the data can be interpreted in any form while the "truth" is kept. This gives our best image of an ideal knowledge base by far, that sorting of information, planning of project and goals as well as creating of knowledge can be all together.
|
Timothy Berners-Lee once taught us about the idea of the semantic web, where all the data can be interpreted in any form while the "truth" is kept. This gives our best image of an ideal knowledge base by far, that sorting of information, planning of project and goals as well as creating of knowledge can be all together.
|
||||||
We have witnessed waves of paradigm shift so many times. At first, everything was noted on office-like apps or DSL like LaTeX, then we found todo-list apps and WYSIWYG markdown editors better for writing and planning. Finally, here comes Notion and Miro, who take advantage of the idea of blocks to further liberate our creativity.
|
We have witnessed waves of paradigm shift so many times. At first, everything was noted on office-like apps or DSL like LaTeX, then we found todo-list apps and WYSIWYG markdown editors better for writing and planning. Finally, here comes Notion and Miro, who take advantage of the idea of blocks to further liberate our creativity.
|
||||||
It is all perfect... If there are not so many waste operations and redundant information. And, we insist that privacy first should always be given by default.
|
It is all perfect... without waste operations and redundant information. And, we insist that privacy first should always be given by default.
|
||||||
That's why we are making AFFiNE. Some of the most important features are:
|
That's why we are making AFFiNE. Some of the most important features are:
|
||||||
|
|
||||||
- Transformable
|
- Transformable
|
||||||
- Every block can be transformed equally well as a database
|
- Every block can be transformed equally
|
||||||
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
- e.g. you can create a todo in Markdown in the text view and then later edit it in the kanban view.
|
||||||
- Every doc can be turned into a whiteboard
|
- Every document can be turned into a whiteboard
|
||||||
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
- An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity.
|
||||||
- Atomic
|
- Atomic
|
||||||
- The basic element of affine are blocks, not pages.
|
- The basic elements of AFFiNE are blocks, not pages.
|
||||||
- Blocks can be directly reused and synced between pages.
|
- Blocks can be directly reused and synced between pages.
|
||||||
- Pages and blocks are searched and organized based on connected graphs, not tree-like paths.
|
- Pages and blocks are searched and organized based on connected graphs, not tree-like paths.
|
||||||
- Dual-link and semantic search are fully supported.
|
- Dual-link and semantic search are fully supported.
|
||||||
@@ -136,8 +154,7 @@ That's why we are making AFFiNE. Some of the most important features are:
|
|||||||
- Data is always stored locally by default
|
- Data is always stored locally by default
|
||||||
- CRDTs are applied so that peer-to-peer collaboration is possible.
|
- CRDTs are applied so that peer-to-peer collaboration is possible.
|
||||||
|
|
||||||
We really appreciate the idea of Monday, Airtable and Notion databases. They inspired what we think is right for task management. But we don't like the repeated works -- we don't want to set a todo easily with markdown but end up re-write it again in kanban or other databases.
|
We appreciate the ideas of Monday, Airtable, and Notion databases. They have inspired us and shaped our product, helping us get it right when it comes to task management. But we also do things differently. We don't like doing things again and again. It's easy to set a todo with Markdown, but then why do you need to repeat and recreate data for a kanban or other databases. This is the power of AFFiNE. With AFFiNE, every block group has infinite views, for you to keep your single source of data, a signle source of truth.
|
||||||
With AFFiNE, every block group has infinite views, for you to keep your single source of truth.
|
|
||||||
|
|
||||||
We would like to give special thanks to the innovators and pioneers who greatly inspired us:
|
We would like to give special thanks to the innovators and pioneers who greatly inspired us:
|
||||||
|
|
||||||
@@ -172,19 +189,35 @@ For help, discussion about best practices, or any other conversation that would
|
|||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://darksky.eu.org/"><img src="https://avatars.githubusercontent.com/u/25152247?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DarkSky</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://darksky.eu.org/"><img src="https://avatars.githubusercontent.com/u/25152247?v=4?s=50" width="50px;" alt=""/><br /><sub><b>DarkSky</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=darkskygit" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="http://zhangchi.page/"><img src="https://avatars.githubusercontent.com/u/5910926?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Chi Zhang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Documentation">📖</a></td>
|
<td align="center"><a href="http://zhangchi.page/"><img src="https://avatars.githubusercontent.com/u/5910926?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Chi Zhang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=tzhangchi" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/alt1o"><img src="https://avatars.githubusercontent.com/u/21084335?v=4?s=100" width="100px;" alt=""/><br /><sub><b>wang xinglong</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/alt1o"><img src="https://avatars.githubusercontent.com/u/21084335?v=4?s=50" width="50px;" alt=""/><br /><sub><b>wang xinglong</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=alt1o" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/DiamondThree"><img src="https://avatars.githubusercontent.com/u/24630517?v=4?s=100" width="100px;" alt=""/><br /><sub><b>DiamondThree</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/DiamondThree"><img src="https://avatars.githubusercontent.com/u/24630517?v=4?s=50" width="50px;" alt=""/><br /><sub><b>DiamondThree</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=DiamondThree" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://lawvs.github.io/profile/"><img src="https://avatars.githubusercontent.com/u/18554747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Whitewater</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://lawvs.github.io/profile/"><img src="https://avatars.githubusercontent.com/u/18554747?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Whitewater</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=lawvs" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=100" width="100px;" alt=""/><br /><sub><b>xiaodong zuo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/zuoxiaodong0815"><img src="https://avatars.githubusercontent.com/u/53252747?v=4?s=50" width="50px;" alt=""/><br /><sub><b>xiaodong zuo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=zuoxiaodong0815" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=100" width="100px;" alt=""/><br /><sub><b>MingLIang Wang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/SaikaSakura"><img src="https://avatars.githubusercontent.com/u/11530942?v=4?s=50" width="50px;" alt=""/><br /><sub><b>MingLIang Wang</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=SaikaSakura" title="Documentation">📖</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/QiShaoXuan"><img src="https://avatars.githubusercontent.com/u/22772830?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Qi</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=QiShaoXuan" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/mitsuhatu"><img src="https://avatars.githubusercontent.com/u/110213079?v=4?s=100" width="100px;" alt=""/><br /><sub><b>mitsuhatu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/mitsuhatu"><img src="https://avatars.githubusercontent.com/u/110213079?v=4?s=50" width="50px;" alt=""/><br /><sub><b>mitsuhatu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=mitsuhatu" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://shockwave.me/"><img src="https://avatars.githubusercontent.com/u/15013925?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Austaras</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=Austaras" title="Documentation">📖</a></td>
|
||||||
<td align="center"><a href="https://github.com/uptonking?tab=repositories&type=source"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
|
<td align="center"><a href="https://github.com/uptonking"><img src="https://avatars.githubusercontent.com/u/11391549?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Jin Yao</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Code">💻</a> <a href="https://github.com/toeverything/AFFiNE/commits?author=uptonking" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/HeJiachen-PM"><img src="https://avatars.githubusercontent.com/u/79301703?v=4?s=50" width="50px;" alt=""/><br /><sub><b>HeJiachen-PM</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=HeJiachen-PM" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/Yipei-Operation"><img src="https://avatars.githubusercontent.com/u/79373028?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Yipei Wei</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=Yipei-Operation" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/fanjing22"><img src="https://avatars.githubusercontent.com/u/109729699?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fanjing22</b></sub></a><br /><a href="#design-fanjing22" title="Design">🎨</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/Svaney-ssman"><img src="https://avatars.githubusercontent.com/u/110808979?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Svaney</b></sub></a><br /><a href="#design-Svaney-ssman" title="Design">🎨</a></td>
|
||||||
|
<td align="center"><a href="http://xell.me/"><img src="https://avatars.githubusercontent.com/u/132558?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Guozhu Liu</b></sub></a><br /><a href="#design-xell" title="Design">🎨</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/fyZheng07"><img src="https://avatars.githubusercontent.com/u/63830919?v=4?s=50" width="50px;" alt=""/><br /><sub><b>fyZheng07</b></sub></a><br /><a href="#eventOrganizing-fyZheng07" title="Event Organizing">📋</a> <a href="#userTesting-fyZheng07" title="User Testing">📓</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/CJSS"><img src="https://avatars.githubusercontent.com/u/4605025?v=4?s=50" width="50px;" alt=""/><br /><sub><b>CJSS</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=CJSS" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/clean-software"><img src="https://avatars.githubusercontent.com/u/62192072?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Carlos Rafael </b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=CarlosZoft" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/caleboleary"><img src="https://avatars.githubusercontent.com/u/12816579?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Caleb OLeary</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=caleboleary" title="Code">💻</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/JimmFly"><img src="https://avatars.githubusercontent.com/u/102217452?v=4?s=50" width="50px;" alt=""/><br /><sub><b>JimmFly</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=JimmFly" title="Code">💻</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"><a href="https://github.com/westongraham"><img src="https://avatars.githubusercontent.com/u/89493023?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Weston Graham</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=westongraham" title="Documentation">📖</a></td>
|
||||||
|
<td align="center"><a href="https://github.com/pointmax"><img src="https://avatars.githubusercontent.com/u/49361135?v=4?s=50" width="50px;" alt=""/><br /><sub><b>pointmax</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=pointmax" title="Documentation">📖</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ describe('ligo-virgo', () => {
|
|||||||
beforeEach(() => cy.visit('/'));
|
beforeEach(() => cy.visit('/'));
|
||||||
|
|
||||||
it('basic load check', () => {
|
it('basic load check', () => {
|
||||||
getTitle().contains('Get Started with AFFiNE');
|
getTitle().contains('👋 Get Started with AFFINE');
|
||||||
|
|
||||||
cy.get('.block_container').contains('The Essentials');
|
cy.get('.block_container').contains('The Essentials');
|
||||||
|
|
||||||
getBoard().click();
|
getBoard().click();
|
||||||
cy.get('.tl-inner-div').contains('Graduating');
|
cy.get('.tl-inner-div').contains('The Essentials');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export function RoutePrivate({
|
|||||||
return <PageLoading />;
|
return <PageLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user) {
|
if (!user || !pathname.startsWith(`/${user.id}`)) {
|
||||||
return (
|
return (
|
||||||
<Navigate
|
<Navigate
|
||||||
to={unauthorizedRedirectTo}
|
to={unauthorizedRedirectTo}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as uiIcons from '@toeverything/components/icons';
|
|||||||
import { message, styled } from '@toeverything/components/ui';
|
import { message, styled } from '@toeverything/components/ui';
|
||||||
import { copy } from './copy';
|
import { copy } from './copy';
|
||||||
|
|
||||||
const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
|
const IconBooth = ({ name, Icon }: { name: string; Icon: FC<any> }) => {
|
||||||
const on_click = () => {
|
const on_click = () => {
|
||||||
copy(`<${name} />`);
|
copy(`<${name} />`);
|
||||||
message.success('Copied ~');
|
message.success('Copied ~');
|
||||||
@@ -18,7 +18,7 @@ const IconBooth: FC<{ name: string; Icon: FC<any> }> = ({ name, Icon }) => {
|
|||||||
|
|
||||||
const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp');
|
const _icons = Object.entries(uiIcons).filter(([key]) => key !== 'timestamp');
|
||||||
|
|
||||||
export const Icons: FC = () => {
|
export const Icons = () => {
|
||||||
const ref = useRef<HTMLHeadingElement>(null);
|
const ref = useRef<HTMLHeadingElement>(null);
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export function WorkspaceHome() {
|
|||||||
workspace_id,
|
workspace_id,
|
||||||
user_initial_page_id,
|
user_initial_page_id,
|
||||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||||
name: null,
|
name: '👋 Get Started with AFFINE',
|
||||||
groupKeys: ['todolist'],
|
groupKeys: ['getStartedGroup0', 'getStartedGroup1'],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
/* eslint-disable filename-rules/match */
|
/* eslint-disable filename-rules/match */
|
||||||
import { useEffect, useRef, type UIEvent, useState } from 'react';
|
import { useEffect, useRef, type UIEvent, useState } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import {
|
|
||||||
MuiBox as Box,
|
|
||||||
MuiCircularProgress as CircularProgress,
|
|
||||||
styled,
|
|
||||||
} from '@toeverything/components/ui';
|
|
||||||
import { AffineEditor } from '@toeverything/components/affine-editor';
|
import { AffineEditor } from '@toeverything/components/affine-editor';
|
||||||
import {
|
import {
|
||||||
CalendarHeatmap,
|
CalendarHeatmap,
|
||||||
@@ -15,10 +11,13 @@ import {
|
|||||||
import { CollapsibleTitle } from '@toeverything/components/common';
|
import { CollapsibleTitle } from '@toeverything/components/common';
|
||||||
import {
|
import {
|
||||||
useShowSpaceSidebar,
|
useShowSpaceSidebar,
|
||||||
useUserAndSpaces,
|
|
||||||
usePageClientWidth,
|
usePageClientWidth,
|
||||||
} from '@toeverything/datasource/state';
|
} from '@toeverything/datasource/state';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import {
|
||||||
|
MuiBox as Box,
|
||||||
|
MuiCircularProgress as CircularProgress,
|
||||||
|
styled,
|
||||||
|
} from '@toeverything/components/ui';
|
||||||
|
|
||||||
import { WorkspaceName } from './workspace-name';
|
import { WorkspaceName } from './workspace-name';
|
||||||
import { CollapsiblePageTree } from './collapsible-page-tree';
|
import { CollapsiblePageTree } from './collapsible-page-tree';
|
||||||
|
|||||||
@@ -1,34 +1,34 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
|
||||||
import { useNavigate, useParams } from 'react-router-dom';
|
|
||||||
import {
|
|
||||||
MuiBox as Box,
|
|
||||||
MuiButton as Button,
|
|
||||||
MuiCollapse as Collapse,
|
|
||||||
MuiIconButton as IconButton,
|
|
||||||
styled,
|
|
||||||
} from '@toeverything/components/ui';
|
|
||||||
import {
|
import {
|
||||||
|
AddIcon,
|
||||||
ArrowDropDownIcon,
|
ArrowDropDownIcon,
|
||||||
ArrowRightIcon,
|
ArrowRightIcon,
|
||||||
} from '@toeverything/components/icons';
|
} from '@toeverything/components/icons';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
|
||||||
import {
|
import {
|
||||||
usePageTree,
|
|
||||||
useCalendarHeatmap,
|
useCalendarHeatmap,
|
||||||
|
usePageTree,
|
||||||
} from '@toeverything/components/layout';
|
} from '@toeverything/components/layout';
|
||||||
import { AddIcon } from '@toeverything/components/icons';
|
import {
|
||||||
|
IconButton,
|
||||||
|
MuiBox as Box,
|
||||||
|
MuiCollapse as Collapse,
|
||||||
|
styled,
|
||||||
|
} from '@toeverything/components/ui';
|
||||||
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
const StyledContainer = styled('div')({
|
const StyledBtn = styled('div')({
|
||||||
height: '32px',
|
height: '32px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
});
|
|
||||||
|
|
||||||
const StyledBtn = styled('div')({
|
|
||||||
color: '#98ACBD',
|
color: '#98ACBD',
|
||||||
textTransform: 'none',
|
textTransform: 'none',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
flex: 1,
|
||||||
|
marginLeft: '12px',
|
||||||
});
|
});
|
||||||
|
|
||||||
export type CollapsiblePageTreeProps = {
|
export type CollapsiblePageTreeProps = {
|
||||||
@@ -72,7 +72,7 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
paddingRight: 1,
|
paddingRight: '12px',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: '#f5f7f8',
|
background: '#f5f7f8',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
@@ -81,27 +81,18 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
|||||||
onMouseEnter={() => setNewPageBtnVisible(true)}
|
onMouseEnter={() => setNewPageBtnVisible(true)}
|
||||||
onMouseLeave={() => setNewPageBtnVisible(false)}
|
onMouseLeave={() => setNewPageBtnVisible(false)}
|
||||||
>
|
>
|
||||||
<StyledContainer>
|
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
||||||
{open ? (
|
{title}
|
||||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
</StyledBtn>
|
||||||
) : (
|
|
||||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
|
||||||
)}
|
|
||||||
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
|
||||||
{title}
|
|
||||||
</StyledBtn>
|
|
||||||
</StyledContainer>
|
|
||||||
|
|
||||||
{newPageBtnVisible && (
|
{newPageBtnVisible && (
|
||||||
<AddIcon
|
<IconButton
|
||||||
style={{
|
size="small"
|
||||||
width: '20px',
|
hoverColor="#E0E6EB"
|
||||||
height: '20px',
|
|
||||||
color: '#98ACBD',
|
|
||||||
cursor: 'pointer',
|
|
||||||
}}
|
|
||||||
onClick={create_page}
|
onClick={create_page}
|
||||||
/>
|
>
|
||||||
|
<AddIcon />
|
||||||
|
</IconButton>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{children ? (
|
{children ? (
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ const StyledTabs = styled('div')(({ theme }) => {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: theme.affine.palette.primary,
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledTabTitle = styled('div', {
|
const StyledTabTitle = styled('div')<{
|
||||||
shouldForwardProp: (prop: string) => !['isActive'].includes(prop),
|
isActive?: boolean;
|
||||||
})<{ isActive?: boolean; isDisabled?: boolean }>`
|
isDisabled?: boolean;
|
||||||
|
}>`
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -26,13 +26,18 @@ const StyledTabTitle = styled('div', {
|
|||||||
padding-top: 4px;
|
padding-top: 4px;
|
||||||
border-top: 2px solid #ecf1fb;
|
border-top: 2px solid #ecf1fb;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
color: ${({ theme, isActive }) =>
|
||||||
|
isActive ? theme.affine.palette.primary : 'rgba(62, 111, 219, 0.6)'};
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background-color: ${({ isActive, theme }) =>
|
background-color: ${({ isActive, theme }) =>
|
||||||
isActive ? theme.affine.palette.primary : ''};
|
isActive
|
||||||
|
? theme.affine.palette.primary
|
||||||
|
: 'rgba(62, 111, 219, 0.6)'};
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 100%;
|
left: 100%;
|
||||||
top: -2px;
|
top: -2px;
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import {
|
import { styled, Input } from '@toeverything/components/ui';
|
||||||
styled,
|
|
||||||
MuiOutlinedInput as OutlinedInput,
|
|
||||||
} from '@toeverything/components/ui';
|
|
||||||
import { PinIcon } from '@toeverything/components/icons';
|
import { PinIcon } from '@toeverything/components/icons';
|
||||||
import {
|
import {
|
||||||
useUserAndSpaces,
|
useUserAndSpaces,
|
||||||
useShowSpaceSidebar,
|
useShowSpaceSidebar,
|
||||||
} from '@toeverything/datasource/state';
|
} from '@toeverything/datasource/state';
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, {
|
||||||
|
ChangeEvent,
|
||||||
|
KeyboardEvent,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { Logo } from './components/logo/Logo';
|
import { Logo } from './components/logo/Logo';
|
||||||
|
|
||||||
@@ -89,18 +92,16 @@ export const WorkspaceName = () => {
|
|||||||
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
||||||
const [inRename, setInRename] = useState(false);
|
const [inRename, setInRename] = useState(false);
|
||||||
const [workspaceName, setWorkspaceName] = useState('');
|
const [workspaceName, setWorkspaceName] = useState('');
|
||||||
const [workspaceId, setWorkspaceId] = useState('');
|
|
||||||
|
|
||||||
const fetchWorkspaceName = useCallback(async () => {
|
const fetchWorkspaceName = useCallback(async () => {
|
||||||
if (!currentSpaceId) {
|
if (!currentSpaceId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const [name, workspaceId] = await Promise.all([
|
const name = await services.api.userConfig.getWorkspaceName(
|
||||||
services.api.userConfig.getWorkspaceName(currentSpaceId),
|
currentSpaceId
|
||||||
services.api.userConfig.getWorkspaceId(currentSpaceId),
|
);
|
||||||
]);
|
|
||||||
setWorkspaceName(name);
|
setWorkspaceName(name);
|
||||||
setWorkspaceId(workspaceId);
|
|
||||||
}, [currentSpaceId]);
|
}, [currentSpaceId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -124,24 +125,24 @@ export const WorkspaceName = () => {
|
|||||||
};
|
};
|
||||||
}, [currentSpaceId, fetchWorkspaceName]);
|
}, [currentSpaceId, fetchWorkspaceName]);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
if (e.key === 'Enter') {
|
||||||
if (e.key === 'Enter') {
|
e.stopPropagation();
|
||||||
e.stopPropagation();
|
e.preventDefault();
|
||||||
e.preventDefault();
|
setInRename(false);
|
||||||
setInRename(false);
|
}
|
||||||
}
|
}, []);
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
async (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
services.api.userConfig.setWorkspaceName(
|
const name = e.target.value;
|
||||||
|
|
||||||
|
await setWorkspaceName(name);
|
||||||
|
await services.api.userConfig.setWorkspaceName(
|
||||||
currentSpaceId,
|
currentSpaceId,
|
||||||
e.currentTarget.value
|
name
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[]
|
[currentSpaceId]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -165,7 +166,8 @@ export const WorkspaceName = () => {
|
|||||||
<StyledWorkspace>
|
<StyledWorkspace>
|
||||||
{inRename ? (
|
{inRename ? (
|
||||||
<WorkspaceReNameContainer>
|
<WorkspaceReNameContainer>
|
||||||
<OutlinedInput
|
<Input
|
||||||
|
autoFocus
|
||||||
style={{ width: '140px', height: '28px' }}
|
style={{ width: '140px', height: '28px' }}
|
||||||
value={workspaceName}
|
value={workspaceName}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -176,7 +178,7 @@ export const WorkspaceName = () => {
|
|||||||
) : (
|
) : (
|
||||||
<WorkspaceNameContainer>
|
<WorkspaceNameContainer>
|
||||||
<span onClick={() => setInRename(true)}>
|
<span onClick={() => setInRename(true)}>
|
||||||
{workspaceName || workspaceId}
|
{workspaceName || currentSpaceId}
|
||||||
</span>
|
</span>
|
||||||
</WorkspaceNameContainer>
|
</WorkspaceNameContainer>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -91,9 +91,9 @@ module.exports = function (webpackConfig) {
|
|||||||
priority: -9,
|
priority: -9,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
vender: {
|
vendor: {
|
||||||
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
||||||
name: 'vender',
|
name: 'vendor',
|
||||||
priority: -10,
|
priority: -10,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
|||||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||||
import RedditIcon from '@mui/icons-material/Reddit';
|
import RedditIcon from '@mui/icons-material/Reddit';
|
||||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||||
|
import { LogoIcon } from '@toeverything/components/icons';
|
||||||
// eslint-disable-next-line no-restricted-imports
|
// eslint-disable-next-line no-restricted-imports
|
||||||
import { useMediaQuery } from '@mui/material';
|
import { useMediaQuery } from '@mui/material';
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ const DiscordIcon = (props: any) => {
|
|||||||
viewBox="0 0 71 55"
|
viewBox="0 0 71 55"
|
||||||
fill="currentcolor"
|
fill="currentcolor"
|
||||||
>
|
>
|
||||||
<g clip-path="url(#clip0)">
|
<g clipPath="url(#clip0)">
|
||||||
<path
|
<path
|
||||||
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z"
|
||||||
fill="currentcolor"
|
fill="currentcolor"
|
||||||
@@ -203,9 +204,7 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => {
|
|||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.replace(
|
window.open('https://github.com/toeverything/AFFiNE');
|
||||||
'https://github.com/toeverything/AFFiNE'
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
{...(props.flat ? { variant: 'plain' } : {})}
|
{...(props.flat ? { variant: 'plain' } : {})}
|
||||||
{...{
|
{...{
|
||||||
@@ -236,7 +235,50 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => {
|
|||||||
startIcon={<GitHubIcon />}
|
startIcon={<GitHubIcon />}
|
||||||
size="lg"
|
size="lg"
|
||||||
>
|
>
|
||||||
GitHub
|
{props.center ? 'Check ' : ''}GitHub
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AFFiNEOnline = (props: { center?: boolean; flat?: boolean }) => {
|
||||||
|
const matches = useMediaQuery('(max-width: 1024px)');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
window.open('https://livedemo.affine.pro/');
|
||||||
|
}}
|
||||||
|
{...(props.flat ? { variant: 'plain' } : {})}
|
||||||
|
{...{
|
||||||
|
sx: {
|
||||||
|
margin: 'auto 1em',
|
||||||
|
fontSize: '24px',
|
||||||
|
'@media (max-width: 1024px)': {
|
||||||
|
fontSize: '16px',
|
||||||
|
},
|
||||||
|
...(props.flat
|
||||||
|
? {
|
||||||
|
padding: matches ? '0' : '0 0.5em',
|
||||||
|
':hover': { backgroundColor: 'unset' },
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
...(props.center
|
||||||
|
? {
|
||||||
|
padding: '0.5em 1em',
|
||||||
|
fontSize: '2em',
|
||||||
|
backgroundColor: '#000',
|
||||||
|
':hover': {
|
||||||
|
backgroundColor: '#0c60d9',
|
||||||
|
boxShadow: '2px 2px 20px #08f4',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
startIcon={<LogoIcon />}
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Try it Online
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -258,7 +300,10 @@ export function App() {
|
|||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={2}
|
spacing={2}
|
||||||
sx={{ maxWidth: '1280px', margin: 'auto' }}
|
sx={{
|
||||||
|
maxWidth: '1280px',
|
||||||
|
margin: 'auto',
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Grid xs={6}>
|
<Grid xs={6}>
|
||||||
<Button
|
<Button
|
||||||
@@ -283,9 +328,7 @@ export function App() {
|
|||||||
<GitHub flat />
|
<GitHub flat />
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.location.replace(
|
window.open('https://blog.affine.pro');
|
||||||
'https://blog.affine.pro'
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
variant="plain"
|
variant="plain"
|
||||||
sx={{
|
sx={{
|
||||||
@@ -407,10 +450,8 @@ export function App() {
|
|||||||
marginBottom: '12vh!important',
|
marginBottom: '12vh!important',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography level="h2" sx={{ alignSelf: 'center' }}>
|
|
||||||
Try it on
|
|
||||||
</Typography>
|
|
||||||
<GitHub center />
|
<GitHub center />
|
||||||
|
<AFFiNEOnline center />
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
@@ -447,7 +488,7 @@ export function App() {
|
|||||||
level={matches ? 'h2' : 'h1'}
|
level={matches ? 'h2' : 'h1'}
|
||||||
fontWeight={'bold'}
|
fontWeight={'bold'}
|
||||||
>
|
>
|
||||||
It’s not just add-up of Docs, whiteboard, and
|
It’s not just a collection of Docs, whiteboard, and
|
||||||
tables.
|
tables.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -465,11 +506,11 @@ export function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography fontSize="1.2em">
|
<Typography fontSize="1.2em">
|
||||||
Transform any building blocks as you like.
|
Transform any building block as you like.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography fontSize="1.2em">
|
<Typography fontSize="1.2em">
|
||||||
Say goodbye to redundancy, and keep all your
|
Say goodbye to redundancy. Store your data once, and
|
||||||
knowledge minimal, in your way.
|
keep your data as you like it.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -512,15 +553,17 @@ export function App() {
|
|||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
Docs, kanban, and databases are all fully
|
Docs, Kanbans, and Databases are all fully
|
||||||
functional at any place. You always keep
|
functional anywhere, anytime. A truly
|
||||||
what-you-see-is-what-you-get.
|
what-you-see-is-what-you-get environment for
|
||||||
|
your data.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
All pages come with docs and whiteboard mode.
|
All pages come with a document (Paper Mode) and
|
||||||
|
whiteboard (Edgeless Mode) view.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -588,21 +631,21 @@ export function App() {
|
|||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
No more chaos between so many views.
|
No more chaos managing multiple views.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
Set a TODO with Markdown, and manage it in
|
Set a TODO with Markdown, and seamlessly edit it
|
||||||
Kanban.
|
within a Kanban.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
Managing multi-dimensional tables should be
|
Managing multi-dimensional tables should be this
|
||||||
simple as that.
|
simple – and now it is.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -661,16 +704,20 @@ export function App() {
|
|||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
We don’t like cloud lock-in. Your data is always
|
We don’t like being locked-in, and neither should
|
||||||
locally stored and secured, while you can still
|
you. Privacy is at the foundation of everything we
|
||||||
collaborate in real-time with others.
|
do, but it should not limit us that’s+ why there are
|
||||||
|
no compromises.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
fontSize="1.2em"
|
fontSize="1.2em"
|
||||||
style={{ marginBottom: '0.25em' }}
|
style={{ marginBottom: '0.25em' }}
|
||||||
>
|
>
|
||||||
Privacy is the foundation of everything we do. But
|
Your data is yours; it is always locally stored and
|
||||||
it is no excuse for the bad experience.
|
secured - available to you always. While still being
|
||||||
|
able enjoy collaboration features such as real-time
|
||||||
|
editing and sharing with others, without any cloud
|
||||||
|
setup.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -81,9 +81,9 @@ module.exports = function (webpackConfig) {
|
|||||||
priority: -9,
|
priority: -9,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
vender: {
|
vendor: {
|
||||||
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
||||||
name: 'vender',
|
name: 'vendor',
|
||||||
priority: -10,
|
priority: -10,
|
||||||
chunks: 'all',
|
chunks: 'all',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable filename-rules/match */
|
/* eslint-disable filename-rules/match */
|
||||||
import { useCallback, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { initializeApp } from 'firebase/app';
|
import { initializeApp } from 'firebase/app';
|
||||||
import {
|
import {
|
||||||
GoogleAuthProvider,
|
GoogleAuthProvider,
|
||||||
@@ -8,15 +8,7 @@ import {
|
|||||||
browserLocalPersistence,
|
browserLocalPersistence,
|
||||||
} from 'firebase/auth';
|
} from 'firebase/auth';
|
||||||
|
|
||||||
import { LogoImg } from '@toeverything/components/common';
|
import { MuiButton } from '@toeverything/components/ui';
|
||||||
import {
|
|
||||||
MuiButton,
|
|
||||||
MuiBox,
|
|
||||||
MuiGrid,
|
|
||||||
MuiSnackbar,
|
|
||||||
} from '@toeverything/components/ui';
|
|
||||||
|
|
||||||
import { Error } from './../error';
|
|
||||||
|
|
||||||
const _firebaseConfig = {
|
const _firebaseConfig = {
|
||||||
apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
|
apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
|
||||||
@@ -75,7 +67,7 @@ const GoogleIcon = () => (
|
|||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Firebase = () => {
|
export const Firebase = (props: { onError: () => void }) => {
|
||||||
const [auth, provider] = useMemo(() => {
|
const [auth, provider] = useMemo(() => {
|
||||||
const auth = getAuth(_app);
|
const auth = getAuth(_app);
|
||||||
auth.setPersistence(browserLocalPersistence);
|
auth.setPersistence(browserLocalPersistence);
|
||||||
@@ -83,8 +75,6 @@ export const Firebase = () => {
|
|||||||
return [auth, provider];
|
return [auth, provider];
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const [error, setError] = useState(false);
|
|
||||||
|
|
||||||
const handleAuth = useCallback(() => {
|
const handleAuth = useCallback(() => {
|
||||||
signInWithPopup(auth, provider).catch(error => {
|
signInWithPopup(auth, provider).catch(error => {
|
||||||
const errorCode = error.code;
|
const errorCode = error.code;
|
||||||
@@ -92,53 +82,19 @@ export const Firebase = () => {
|
|||||||
const email = error.customData.email;
|
const email = error.customData.email;
|
||||||
const credential = GoogleAuthProvider.credentialFromError(error);
|
const credential = GoogleAuthProvider.credentialFromError(error);
|
||||||
console.log(errorCode, errorMessage, email, credential);
|
console.log(errorCode, errorMessage, email, credential);
|
||||||
setError(true);
|
props.onError();
|
||||||
setTimeout(() => setError(false), 3000);
|
|
||||||
});
|
});
|
||||||
}, [auth, provider]);
|
}, [auth, props, provider]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MuiGrid container>
|
<MuiButton
|
||||||
<MuiSnackbar
|
variant="outlined"
|
||||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
fullWidth
|
||||||
open={error}
|
style={{ textTransform: 'none' }}
|
||||||
message="Login failed, please check if you have permission"
|
startIcon={<GoogleIcon />}
|
||||||
/>
|
onClick={handleAuth}
|
||||||
<MuiGrid item xs={8}>
|
>
|
||||||
<Error
|
Continue with Google
|
||||||
title="Welcome to AFFiNE"
|
</MuiButton>
|
||||||
subTitle="blocks of knowledge to power your team"
|
|
||||||
action1Text="Login or Register"
|
|
||||||
/>
|
|
||||||
</MuiGrid>
|
|
||||||
|
|
||||||
<MuiGrid item xs={4}>
|
|
||||||
<MuiBox
|
|
||||||
onSubmit={handleAuth}
|
|
||||||
onClick={handleAuth}
|
|
||||||
style={{
|
|
||||||
textAlign: 'center',
|
|
||||||
width: '300px',
|
|
||||||
margin: '300px auto 20px auto',
|
|
||||||
}}
|
|
||||||
sx={{ mt: 1 }}
|
|
||||||
>
|
|
||||||
<LogoImg
|
|
||||||
style={{
|
|
||||||
width: '100px',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<MuiButton
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
style={{ textTransform: 'none' }}
|
|
||||||
startIcon={<GoogleIcon />}
|
|
||||||
>
|
|
||||||
Continue with Google
|
|
||||||
</MuiButton>
|
|
||||||
</MuiBox>
|
|
||||||
</MuiGrid>
|
|
||||||
</MuiGrid>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/* eslint-disable filename-rules/match */
|
||||||
|
import { useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
|
import { LogoIcon } from '@toeverything/components/icons';
|
||||||
|
import { MuiButton } from '@toeverything/components/ui';
|
||||||
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
|
import { useLocalTrigger } from '@toeverything/datasource/state';
|
||||||
|
|
||||||
|
const cleanupWorkspace = (workspace: string) =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const req = indexedDB.deleteDatabase(workspace);
|
||||||
|
req.addEventListener('error', e => reject(e));
|
||||||
|
req.addEventListener('blocked', e => reject(e));
|
||||||
|
req.addEventListener('upgradeneeded', e => reject(e));
|
||||||
|
req.addEventListener('success', e => resolve(e));
|
||||||
|
});
|
||||||
|
|
||||||
|
const requestPermission = async (workspace: string) => {
|
||||||
|
await cleanupWorkspace(workspace);
|
||||||
|
// @ts-ignore
|
||||||
|
const dirHandler = await window.showDirectoryPicker({
|
||||||
|
id: 'AFFiNE_' + workspace,
|
||||||
|
mode: 'readwrite',
|
||||||
|
startIn: 'documents',
|
||||||
|
});
|
||||||
|
const fileHandle = await dirHandler.getFileHandle('affine.db', {
|
||||||
|
create: true,
|
||||||
|
});
|
||||||
|
const file = await fileHandle.getFile();
|
||||||
|
const initialData = new Uint8Array(await file.arrayBuffer());
|
||||||
|
|
||||||
|
const exporter = async (contents: Uint8Array) => {
|
||||||
|
try {
|
||||||
|
const writable = await fileHandle.createWritable();
|
||||||
|
await writable.write(contents);
|
||||||
|
await writable.close();
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await services.api.editorBlock.setupDataExporter(
|
||||||
|
workspace,
|
||||||
|
new Uint8Array(initialData),
|
||||||
|
exporter
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FileSystem = (props: { onError: () => void }) => {
|
||||||
|
const [, onSelected] = useLocalTrigger();
|
||||||
|
|
||||||
|
const apiSupported = useMemo(() => {
|
||||||
|
try {
|
||||||
|
return 'showOpenFilePicker' in window;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MuiButton
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
style={{ textTransform: 'none' }}
|
||||||
|
startIcon={<LogoIcon />}
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
if (apiSupported) {
|
||||||
|
await requestPermission('AFFiNE');
|
||||||
|
onSelected();
|
||||||
|
} else {
|
||||||
|
onSelected();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
props.onError();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{apiSupported ? 'Sync to Disk' : 'Try Live Demo'}
|
||||||
|
</MuiButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,11 +1,63 @@
|
|||||||
|
/* eslint-disable filename-rules/match */
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { LogoImg } from '@toeverything/components/common';
|
||||||
|
import { MuiBox, MuiGrid, MuiSnackbar } from '@toeverything/components/ui';
|
||||||
|
|
||||||
// import { Authing } from './authing';
|
// import { Authing } from './authing';
|
||||||
import { Firebase } from './firebase';
|
import { Firebase } from './firebase';
|
||||||
|
import { FileSystem } from './fs';
|
||||||
|
import { Error } from './../error';
|
||||||
|
|
||||||
export function Login() {
|
export function Login() {
|
||||||
|
const [error, setError] = useState(false);
|
||||||
|
|
||||||
|
const onError = useCallback(() => {
|
||||||
|
setError(true);
|
||||||
|
setTimeout(() => setError(false), 3000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<MuiGrid container>
|
||||||
{/* <Authing /> */}
|
<MuiSnackbar
|
||||||
<Firebase />
|
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||||
</>
|
open={error}
|
||||||
|
message="Request File Permission failed, please check if you have permission"
|
||||||
|
/>
|
||||||
|
<MuiGrid item xs={8}>
|
||||||
|
<Error
|
||||||
|
title="Welcome to AFFiNE"
|
||||||
|
subTitle="blocks of knowledge to power your team"
|
||||||
|
action1Text="Login or Register"
|
||||||
|
/>
|
||||||
|
</MuiGrid>
|
||||||
|
|
||||||
|
<MuiGrid item xs={4}>
|
||||||
|
<MuiBox
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
rowGap: '1em',
|
||||||
|
width: '300px',
|
||||||
|
margin: '300px auto 20px auto',
|
||||||
|
}}
|
||||||
|
sx={{ mt: 1 }}
|
||||||
|
>
|
||||||
|
<LogoImg
|
||||||
|
style={{
|
||||||
|
width: '100px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{(process.env['NX_LOCAL'] && (
|
||||||
|
<FileSystem onError={onError} />
|
||||||
|
)) ||
|
||||||
|
null}
|
||||||
|
{(!process.env['NX_LOCAL'] && (
|
||||||
|
<Firebase onError={onError} />
|
||||||
|
)) ||
|
||||||
|
null}
|
||||||
|
</MuiBox>
|
||||||
|
</MuiGrid>
|
||||||
|
</MuiGrid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import * as React from 'react';
|
import {
|
||||||
|
memo,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
type RefObject,
|
||||||
|
} from 'react';
|
||||||
import { Renderer } from '@tldraw/core';
|
import { Renderer } from '@tldraw/core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import {
|
import {
|
||||||
@@ -132,13 +140,13 @@ export function Tldraw({
|
|||||||
getSession,
|
getSession,
|
||||||
tools,
|
tools,
|
||||||
}: TldrawProps) {
|
}: TldrawProps) {
|
||||||
const [sId, set_sid] = React.useState(id);
|
const [sId, setSid] = useState(id);
|
||||||
const { pageClientWidth } = usePageClientWidth();
|
const { pageClientWidth } = usePageClientWidth();
|
||||||
// page padding left and right total 300px
|
// page padding left and right total 300px
|
||||||
const editorShapeInitSize = pageClientWidth - 300;
|
const editorShapeInitSize = pageClientWidth - 300;
|
||||||
|
|
||||||
// Create a new app when the component mounts.
|
// Create a new app when the component mounts.
|
||||||
const [app, setApp] = React.useState(() => {
|
const [app, setApp] = useState(() => {
|
||||||
const app = new TldrawApp({
|
const app = new TldrawApp({
|
||||||
id,
|
id,
|
||||||
callbacks,
|
callbacks,
|
||||||
@@ -151,7 +159,7 @@ export function Tldraw({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create a new app if the `id` prop changes.
|
// Create a new app if the `id` prop changes.
|
||||||
React.useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (id === sId) return;
|
if (id === sId) return;
|
||||||
const newApp = new TldrawApp({
|
const newApp = new TldrawApp({
|
||||||
id,
|
id,
|
||||||
@@ -161,14 +169,14 @@ export function Tldraw({
|
|||||||
tools,
|
tools,
|
||||||
});
|
});
|
||||||
|
|
||||||
set_sid(id);
|
setSid(id);
|
||||||
|
|
||||||
setApp(newApp);
|
setApp(newApp);
|
||||||
}, [sId, id]);
|
}, [sId, id]);
|
||||||
|
|
||||||
// Update the document if the `document` prop changes but the ids,
|
// Update the document if the `document` prop changes but the ids,
|
||||||
// are the same, or else load a new document if the ids are different.
|
// are the same, or else load a new document if the ids are different.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!document) return;
|
if (!document) return;
|
||||||
|
|
||||||
if (document.id === app.document.id) {
|
if (document.id === app.document.id) {
|
||||||
@@ -179,34 +187,34 @@ export function Tldraw({
|
|||||||
}, [document, app]);
|
}, [document, app]);
|
||||||
|
|
||||||
// Disable assets when the `disableAssets` prop changes.
|
// Disable assets when the `disableAssets` prop changes.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
app.setDisableAssets(disableAssets);
|
app.setDisableAssets(disableAssets);
|
||||||
}, [app, disableAssets]);
|
}, [app, disableAssets]);
|
||||||
|
|
||||||
// Change the page when the `currentPageId` prop changes.
|
// Change the page when the `currentPageId` prop changes.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentPageId) return;
|
if (!currentPageId) return;
|
||||||
app.changePage(currentPageId);
|
app.changePage(currentPageId);
|
||||||
}, [currentPageId, app]);
|
}, [currentPageId, app]);
|
||||||
|
|
||||||
// Toggle the app's readOnly mode when the `readOnly` prop changes.
|
// Toggle the app's readOnly mode when the `readOnly` prop changes.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
app.readOnly = readOnly;
|
app.readOnly = readOnly;
|
||||||
}, [app, readOnly]);
|
}, [app, readOnly]);
|
||||||
|
|
||||||
// Toggle the app's darkMode when the `darkMode` prop changes.
|
// Toggle the app's darkMode when the `darkMode` prop changes.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (darkMode !== app.settings.isDarkMode) {
|
if (darkMode !== app.settings.isDarkMode) {
|
||||||
app.toggleDarkMode();
|
app.toggleDarkMode();
|
||||||
}
|
}
|
||||||
}, [app, darkMode]);
|
}, [app, darkMode]);
|
||||||
|
|
||||||
// Update the app's callbacks when any callback changes.
|
// Update the app's callbacks when any callback changes.
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
app.callbacks = callbacks || {};
|
app.callbacks = callbacks || {};
|
||||||
}, [app, callbacks]);
|
}, [app, callbacks]);
|
||||||
|
|
||||||
React.useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
if (!window.document?.fonts) return;
|
if (!window.document?.fonts) return;
|
||||||
|
|
||||||
@@ -260,7 +268,7 @@ interface InnerTldrawProps {
|
|||||||
showSponsorLink?: boolean;
|
showSponsorLink?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const InnerTldraw = React.memo(function InnerTldraw({
|
const InnerTldraw = memo(function InnerTldraw({
|
||||||
id,
|
id,
|
||||||
autofocus,
|
autofocus,
|
||||||
showPages,
|
showPages,
|
||||||
@@ -276,7 +284,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||||||
}: InnerTldrawProps) {
|
}: InnerTldrawProps) {
|
||||||
const app = useTldrawApp();
|
const app = useTldrawApp();
|
||||||
|
|
||||||
const rWrapper = React.useRef<HTMLDivElement>(null);
|
const rWrapper = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
|
|
||||||
@@ -299,7 +307,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||||||
TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideResizeHandles;
|
TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideResizeHandles;
|
||||||
|
|
||||||
// Custom rendering meta, with dark mode for shapes
|
// Custom rendering meta, with dark mode for shapes
|
||||||
const meta: TDMeta = React.useMemo(() => {
|
const meta: TDMeta = useMemo(() => {
|
||||||
return { isDarkMode: settings.isDarkMode, app };
|
return { isDarkMode: settings.isDarkMode, app };
|
||||||
}, [settings.isDarkMode, app]);
|
}, [settings.isDarkMode, app]);
|
||||||
|
|
||||||
@@ -308,7 +316,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||||||
: appState.selectByContain;
|
: appState.selectByContain;
|
||||||
|
|
||||||
// Custom theme, based on darkmode
|
// Custom theme, based on darkmode
|
||||||
const theme = React.useMemo(() => {
|
const theme = useMemo(() => {
|
||||||
const { selectByContain } = appState;
|
const { selectByContain } = appState;
|
||||||
const { isDarkMode, isCadSelectMode } = settings;
|
const { isDarkMode, isCadSelectMode } = settings;
|
||||||
|
|
||||||
@@ -373,9 +381,11 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||||||
!isSelecting ||
|
!isSelecting ||
|
||||||
!settings.showCloneHandles ||
|
!settings.showCloneHandles ||
|
||||||
pageState.camera.zoom < 0.2;
|
pageState.camera.zoom < 0.2;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledLayout
|
<StyledLayout
|
||||||
ref={rWrapper}
|
ref={rWrapper}
|
||||||
|
panning={settings.forcePanning}
|
||||||
tabIndex={-0}
|
tabIndex={-0}
|
||||||
penColor={app?.appState?.currentStyle?.stroke}
|
penColor={app?.appState?.currentStyle?.stroke}
|
||||||
>
|
>
|
||||||
@@ -477,17 +487,17 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const OneOff = React.memo(function OneOff({
|
const OneOff = memo(function OneOff({
|
||||||
focusableRef,
|
focusableRef,
|
||||||
autofocus,
|
autofocus,
|
||||||
}: {
|
}: {
|
||||||
autofocus?: boolean;
|
autofocus?: boolean;
|
||||||
focusableRef: React.RefObject<HTMLDivElement>;
|
focusableRef: RefObject<HTMLDivElement>;
|
||||||
}) {
|
}) {
|
||||||
useKeyboardShortcuts(focusableRef);
|
useKeyboardShortcuts(focusableRef);
|
||||||
useStylesheet();
|
useStylesheet();
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (autofocus) {
|
if (autofocus) {
|
||||||
focusableRef.current?.focus();
|
focusableRef.current?.focus();
|
||||||
}
|
}
|
||||||
@@ -496,8 +506,8 @@ const OneOff = React.memo(function OneOff({
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const StyledLayout = styled('div')<{ penColor: string }>(
|
const StyledLayout = styled('div')<{ penColor: string; panning: boolean }>(
|
||||||
({ theme, penColor }) => {
|
({ theme, panning, penColor }) => {
|
||||||
return {
|
return {
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
height: '100%',
|
height: '100%',
|
||||||
@@ -509,6 +519,7 @@ const StyledLayout = styled('div')<{ penColor: string }>(
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
outline: 'none',
|
outline: 'none',
|
||||||
|
cursor: panning ? 'grab' : 'unset',
|
||||||
|
|
||||||
'& .tl-container': {
|
'& .tl-container': {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|||||||
@@ -34,10 +34,7 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
|||||||
return max[0];
|
return max[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BorderColorConfig: FC<BorderColorConfigProps> = ({
|
export const BorderColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
|
||||||
app,
|
|
||||||
shapes,
|
|
||||||
}) => {
|
|
||||||
const setBorderColor = (color: string) => {
|
const setBorderColor = (color: string) => {
|
||||||
app.style({ stroke: color }, getShapeIds(shapes));
|
app.style({ stroke: color }, getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import { DeleteShapes } from './DeleteOperation';
|
|||||||
import { Lock, Unlock } from './LockOperation';
|
import { Lock, Unlock } from './LockOperation';
|
||||||
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
||||||
|
|
||||||
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const CommandPanel = ({ app }: { app: TldrawApp }) => {
|
||||||
const state = app.useStore();
|
const state = app.useStore();
|
||||||
const bounds = TLDR.get_selected_bounds(state);
|
const bounds = TLDR.get_selected_bounds(state);
|
||||||
const camera = app.useStore(
|
const camera = app.useStore(
|
||||||
@@ -23,7 +23,7 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
|||||||
? app.getScreenPoint([bounds.minX, bounds.minY])
|
? app.getScreenPoint([bounds.minX, bounds.minY])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const anchor = getAnchor({
|
const anchorEl = getAnchor({
|
||||||
x: point?.[0] || 0,
|
x: point?.[0] || 0,
|
||||||
y: (point?.[1] || 0) + 40,
|
y: (point?.[1] || 0) + 40,
|
||||||
width: bounds?.width ? bounds.width * camera.zoom : 0,
|
width: bounds?.width ? bounds.width * camera.zoom : 0,
|
||||||
@@ -101,7 +101,7 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
|||||||
<Popover
|
<Popover
|
||||||
trigger="click"
|
trigger="click"
|
||||||
visible={!!point}
|
visible={!!point}
|
||||||
anchor={anchor}
|
anchorEl={anchorEl}
|
||||||
popoverDirection="none"
|
popoverDirection="none"
|
||||||
content={
|
content={
|
||||||
<PopoverContainer>
|
<PopoverContainer>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface DeleteShapesProps {
|
|||||||
shapes: TDShape[];
|
shapes: TDShape[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DeleteShapes: FC<DeleteShapesProps> = ({ app, shapes }) => {
|
export const DeleteShapes = ({ app, shapes }: DeleteShapesProps) => {
|
||||||
const deleteShapes = () => {
|
const deleteShapes = () => {
|
||||||
app.delete(getShapeIds(shapes));
|
app.delete(getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
|||||||
return max[0];
|
return max[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FillColorConfig: FC<BorderColorConfigProps> = ({
|
export const FillColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
|
||||||
app,
|
|
||||||
shapes,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setFillColor = (color: ColorType) => {
|
const setFillColor = (color: ColorType) => {
|
||||||
app.style(
|
app.style(
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const _getFontSize = (shapes: TDShape[]): FontSizeStyle => {
|
|||||||
return max[0] as unknown as FontSizeStyle;
|
return max[0] as unknown as FontSizeStyle;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FontSizeConfig: FC<FontSizeConfigProps> = ({ app, shapes }) => {
|
export const FontSizeConfig = ({ app, shapes }: FontSizeConfigProps) => {
|
||||||
const setFontSize = (size: FontSizeStyle) => {
|
const setFontSize = (size: FontSizeStyle) => {
|
||||||
app.style({ fontSize: size }, getShapeIds(shapes));
|
app.style({ fontSize: size }, getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
|||||||
return max[0];
|
return max[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const FrameFillColorConfig: FC<BorderColorConfigProps> = ({
|
export const FrameFillColorConfig = ({
|
||||||
app,
|
app,
|
||||||
shapes,
|
shapes,
|
||||||
}) => {
|
}: BorderColorConfigProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const setFillColor = (color: ColorType) => {
|
const setFillColor = (color: ColorType) => {
|
||||||
app.style(
|
app.style(
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface GroupAndUnGroupProps {
|
|||||||
shapes: TDShape[];
|
shapes: TDShape[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Group: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||||
const group = () => {
|
const group = () => {
|
||||||
app.group(getShapeIds(shapes));
|
app.group(getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
@@ -23,7 +23,7 @@ export const Group: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UnGroup: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
export const UnGroup = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||||
const ungroup = () => {
|
const ungroup = () => {
|
||||||
app.ungroup(getShapeIds(shapes));
|
app.ungroup(getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface GroupAndUnGroupProps {
|
|||||||
shapes: TDShape[];
|
shapes: TDShape[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Lock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
export const Lock = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||||
const lock = () => {
|
const lock = () => {
|
||||||
app.lock(getShapeIds(shapes));
|
app.lock(getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
@@ -23,7 +23,7 @@ export const Lock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Unlock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
export const Unlock = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||||
const unlock = () => {
|
const unlock = () => {
|
||||||
app.unlock(getShapeIds(shapes));
|
app.unlock(getShapeIds(shapes));
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-2
@@ -44,12 +44,12 @@ interface LineStyleProps {
|
|||||||
onStrokeWidthChange: (width: StrokeWidth) => void;
|
onStrokeWidthChange: (width: StrokeWidth) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LineStyle: FC<LineStyleProps> = ({
|
export const LineStyle = ({
|
||||||
strokeStyle,
|
strokeStyle,
|
||||||
onStrokeStyleChange,
|
onStrokeStyleChange,
|
||||||
strokeWidth,
|
strokeWidth,
|
||||||
onStrokeWidthChange,
|
onStrokeWidthChange,
|
||||||
}) => {
|
}: LineStyleProps) => {
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Title>Stroke Style</Title>
|
<Title>Stroke Style</Title>
|
||||||
|
|||||||
+2
-2
@@ -24,10 +24,10 @@ interface BorderColorConfigProps {
|
|||||||
shapes: TDShape[];
|
shapes: TDShape[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StrokeLineStyleConfig: FC<BorderColorConfigProps> = ({
|
export const StrokeLineStyleConfig = ({
|
||||||
app,
|
app,
|
||||||
shapes,
|
shapes,
|
||||||
}) => {
|
}: BorderColorConfigProps) => {
|
||||||
const strokeStyle = _getStrokeStyle(shapes);
|
const strokeStyle = _getStrokeStyle(shapes);
|
||||||
const strokeWidth = _getStrokeWidth(shapes);
|
const strokeWidth = _getStrokeWidth(shapes);
|
||||||
const setStrokeLineStyle = (style: DashStyle) => {
|
const setStrokeLineStyle = (style: DashStyle) => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { FC, ReactNode } from 'react';
|
import type { FC, ReactNode } from 'react';
|
||||||
|
|
||||||
export const ContextMenu: FC<{ children: ReactNode }> = ({ children }) => {
|
export const ContextMenu = ({ children }: { children: ReactNode }) => {
|
||||||
return <div>{children}</div>;
|
return <div>{children}</div>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ const formatColors = (colors: ColorValue[]): ColorObject[] => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Palette: FC<PaletteProps> = ({
|
export const Palette = ({
|
||||||
colors: propColors,
|
colors: propColors,
|
||||||
selected,
|
selected,
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}: PaletteProps) => {
|
||||||
const colorObjects = useMemo(() => formatColors(propColors), [propColors]);
|
const colorObjects = useMemo(() => formatColors(propColors), [propColors]);
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const shapes = [
|
|||||||
|
|
||||||
const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
|
const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
|
||||||
|
|
||||||
export const LineTools: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const LineTools = ({ app }: { app: TldrawApp }) => {
|
||||||
const activeTool = app.useStore(activeToolSelector);
|
const activeTool = app.useStore(activeToolSelector);
|
||||||
|
|
||||||
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ const shapes = [
|
|||||||
|
|
||||||
const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
|
const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
|
||||||
|
|
||||||
export const ShapeTools: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const ShapeTools = ({ app }: { app: TldrawApp }) => {
|
||||||
const activeTool = app.useStore(activeToolSelector);
|
const activeTool = app.useStore(activeToolSelector);
|
||||||
|
|
||||||
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
PopoverContainer,
|
PopoverContainer,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
useTheme,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
import {
|
import {
|
||||||
FrameIcon,
|
FrameIcon,
|
||||||
@@ -67,10 +68,11 @@ const tools: Array<{
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ToolsPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const ToolsPanel = ({ app }: { app: TldrawApp }) => {
|
||||||
const activeTool = app.useStore(activeToolSelector);
|
const activeTool = app.useStore(activeToolSelector);
|
||||||
|
|
||||||
const isToolLocked = app.useStore(toolLockedSelector);
|
const isToolLocked = app.useStore(toolLockedSelector);
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PopoverContainer
|
<PopoverContainer
|
||||||
@@ -105,7 +107,8 @@ export const ToolsPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
|||||||
style={{
|
style={{
|
||||||
color:
|
color:
|
||||||
activeTool === type
|
activeTool === type
|
||||||
? 'blue'
|
? theme.affine.palette
|
||||||
|
.primary
|
||||||
: '',
|
: '',
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ interface PenProps {
|
|||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Pen: FC<PenProps> = ({
|
export const Pen = ({
|
||||||
name,
|
name,
|
||||||
icon,
|
icon,
|
||||||
primaryColor,
|
primaryColor,
|
||||||
secondaryColor,
|
secondaryColor,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}: PenProps) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip content={name}>
|
<Tooltip content={name}>
|
||||||
<StyledIconButton
|
<StyledIconButton
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const PENCIL_CONFIGS_MAP = PENCIL_CONFIGS.reduce<
|
|||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<TDToolType, PencilConfig>);
|
}, {} as Record<TDToolType, PencilConfig>);
|
||||||
|
|
||||||
export const PenTools: FC<{ app: TldrawApp }> = ({ app }) => {
|
export const PenTools = ({ app }: { app: TldrawApp }) => {
|
||||||
const appCurrentTool = app.useStore(state => state.appState.activeTool);
|
const appCurrentTool = app.useStore(state => state.appState.activeTool);
|
||||||
const chosenPen =
|
const chosenPen =
|
||||||
PENCIL_CONFIGS.find(config => config.key === appCurrentTool) ||
|
PENCIL_CONFIGS.find(config => config.key === appCurrentTool) ||
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { MiniMap } from './mini-map';
|
|||||||
const zoomSelector = (s: TDSnapshot) =>
|
const zoomSelector = (s: TDSnapshot) =>
|
||||||
s.document.pageStates[s.appState.currentPageId].camera.zoom;
|
s.document.pageStates[s.appState.currentPageId].camera.zoom;
|
||||||
|
|
||||||
export const ZoomBar: FC = () => {
|
export const ZoomBar = () => {
|
||||||
const app = useTldrawApp();
|
const app = useTldrawApp();
|
||||||
const zoom = app.useStore(zoomSelector);
|
const zoom = app.useStore(zoomSelector);
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const getScaleToMap = (width: number, height: number) => {
|
|||||||
return scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
|
return scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MiniMap: FC = () => {
|
export const MiniMap = () => {
|
||||||
const app = useTldrawApp();
|
const app = useTldrawApp();
|
||||||
const page = app.useStore(s => s.document.pages[s.appState.currentPageId]);
|
const page = app.useStore(s => s.document.pages[s.appState.currentPageId]);
|
||||||
const pageState = app.useStore(
|
const pageState = app.useStore(
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ interface SimplifiedShapeProps extends TLBounds {
|
|||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SimplifiedShape: FC<SimplifiedShapeProps> = ({
|
export const SimplifiedShape = ({
|
||||||
onClick,
|
onClick,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
minX,
|
minX,
|
||||||
minY,
|
minY,
|
||||||
}) => {
|
}: SimplifiedShapeProps) => {
|
||||||
const style: CSSProperties = {
|
const style: CSSProperties = {
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ interface ViewportProps extends TLBounds {
|
|||||||
onPan?: (delta: [number, number]) => void;
|
onPan?: (delta: [number, number]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Viewport: FC<ViewportProps> = ({
|
export const Viewport = ({
|
||||||
onPan,
|
onPan,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
minX,
|
minX,
|
||||||
minY,
|
minY,
|
||||||
}) => {
|
}: ViewportProps) => {
|
||||||
const style: CSSProperties = {
|
const style: CSSProperties = {
|
||||||
width: `${width}px`,
|
width: `${width}px`,
|
||||||
height: `${height}px`,
|
height: `${height}px`,
|
||||||
|
|||||||
@@ -124,10 +124,16 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const activateIfEditing = useCallback(() => {
|
const activateIfEditing = useCallback(() => {
|
||||||
|
const shapes =
|
||||||
|
state.document.pages[state.appState.currentPageId].shapes;
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=1352417
|
||||||
|
if (shapes[shape.id] != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (editingText && editingId !== shape.id) {
|
if (editingText && editingId !== shape.id) {
|
||||||
app.setEditingText(shape.id);
|
app.setEditingText(shape.id);
|
||||||
}
|
}
|
||||||
}, [app, shape.id, editingText, editingId]);
|
}, [app, state, shape.id, editingText, editingId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLContainer ref={ref} {...events}>
|
<HTMLContainer ref={ref} {...events}>
|
||||||
|
|||||||
@@ -219,8 +219,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
isPointing = false;
|
isPointing = false;
|
||||||
|
|
||||||
isForcePanning = false;
|
|
||||||
|
|
||||||
editingStartTime = -1;
|
editingStartTime = -1;
|
||||||
|
|
||||||
fileSystemHandle: FileSystemHandle | null = null;
|
fileSystemHandle: FileSystemHandle | null = null;
|
||||||
@@ -262,7 +260,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
constructor(props: TldrawAppCtorProps) {
|
constructor(props: TldrawAppCtorProps) {
|
||||||
super(
|
super(
|
||||||
TldrawApp.default_state,
|
TldrawApp.defaultState,
|
||||||
props.id,
|
props.id,
|
||||||
TldrawApp.version,
|
TldrawApp.version,
|
||||||
(prev, next, prevVersion) => {
|
(prev, next, prevVersion) => {
|
||||||
@@ -326,9 +324,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
this.patchState({
|
this.patchState({
|
||||||
...TldrawApp.default_state,
|
...TldrawApp.defaultState,
|
||||||
appState: {
|
appState: {
|
||||||
...TldrawApp.default_state.appState,
|
...TldrawApp.defaultState.appState,
|
||||||
status: TDStatus.Idle,
|
status: TDStatus.Idle,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1473,13 +1471,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
this.replace_state(
|
this.replace_state(
|
||||||
{
|
{
|
||||||
...TldrawApp.default_state,
|
...TldrawApp.defaultState,
|
||||||
settings: {
|
settings: {
|
||||||
...this.state.settings,
|
...this.state.settings,
|
||||||
},
|
},
|
||||||
document: migrate(document, TldrawApp.version),
|
document: migrate(document, TldrawApp.version),
|
||||||
appState: {
|
appState: {
|
||||||
...TldrawApp.default_state.appState,
|
...TldrawApp.defaultState.appState,
|
||||||
...this.state.appState,
|
...this.state.appState,
|
||||||
currentPageId: Object.keys(document.pages)[0],
|
currentPageId: Object.keys(document.pages)[0],
|
||||||
disableAssets: this.disableAssets,
|
disableAssets: this.disableAssets,
|
||||||
@@ -3913,7 +3911,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ' ': {
|
case ' ': {
|
||||||
this.isForcePanning = true;
|
this.patchState({
|
||||||
|
settings: {
|
||||||
|
forcePanning: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.spaceKey = true;
|
this.spaceKey = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3976,7 +3978,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ' ': {
|
case ' ': {
|
||||||
this.isForcePanning = false;
|
this.patchState({
|
||||||
|
settings: {
|
||||||
|
forcePanning:
|
||||||
|
this.currentTool.type === TDShapeType.HandDraw,
|
||||||
|
},
|
||||||
|
});
|
||||||
this.spaceKey = false;
|
this.spaceKey = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -4069,13 +4076,18 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
this.pan(delta);
|
this.pan(delta);
|
||||||
|
|
||||||
// When panning, we also want to call onPointerMove, except when "force panning" via spacebar / middle wheel button (it's called elsewhere in that case)
|
// When panning, we also want to call onPointerMove, except when "force panning" via spacebar / middle wheel button (it's called elsewhere in that case)
|
||||||
if (!this.isForcePanning)
|
if (!this.useStore.getState().settings.forcePanning)
|
||||||
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
||||||
};
|
};
|
||||||
|
|
||||||
onZoom: TLWheelEventHandler = (info, e) => {
|
onZoom: TLWheelEventHandler = (info, e) => {
|
||||||
if (this.state.appState.status !== TDStatus.Idle) return;
|
if (this.state.appState.status !== TDStatus.Idle) return;
|
||||||
const delta = info.delta[2] / 50;
|
// Normalize zoom scroll
|
||||||
|
// Fix https://github.com/toeverything/AFFiNE/issues/135
|
||||||
|
const delta =
|
||||||
|
Math.abs(info.delta[2]) > 10
|
||||||
|
? 0.2 * Math.sign(info.delta[2])
|
||||||
|
: info.delta[2] / 50;
|
||||||
this.zoomBy(delta, info.point);
|
this.zoomBy(delta, info.point);
|
||||||
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
||||||
};
|
};
|
||||||
@@ -4093,7 +4105,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
onPointerMove: TLPointerEventHandler = (info, e) => {
|
onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||||
this.previousPoint = this.currentPoint;
|
this.previousPoint = this.currentPoint;
|
||||||
this.updateInputs(info, e);
|
this.updateInputs(info, e);
|
||||||
if (this.isForcePanning && this.isPointing) {
|
if (this.useStore.getState().settings.forcePanning && this.isPointing) {
|
||||||
this.onPan?.(
|
this.onPan?.(
|
||||||
{ ...info, delta: Vec.neg(info.delta) },
|
{ ...info, delta: Vec.neg(info.delta) },
|
||||||
e as unknown as WheelEvent
|
e as unknown as WheelEvent
|
||||||
@@ -4117,20 +4129,23 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
onPointerDown: TLPointerEventHandler = (info, e) => {
|
onPointerDown: TLPointerEventHandler = (info, e) => {
|
||||||
if (e.buttons === 4) {
|
if (e.buttons === 4) {
|
||||||
this.isForcePanning = true;
|
this.patchState({
|
||||||
|
settings: {
|
||||||
|
forcePanning: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
} else if (this.isPointing) {
|
} else if (this.isPointing) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isPointing = true;
|
this.isPointing = true;
|
||||||
this.originPoint = this.getPagePoint(info.point).concat(info.pressure);
|
this.originPoint = this.getPagePoint(info.point).concat(info.pressure);
|
||||||
this.updateInputs(info, e);
|
this.updateInputs(info, e);
|
||||||
if (this.isForcePanning) return;
|
if (this.useStore.getState().settings.forcePanning) return;
|
||||||
this.currentTool.onPointerDown?.(info, e);
|
this.currentTool.onPointerDown?.(info, e);
|
||||||
};
|
};
|
||||||
|
|
||||||
onPointerUp: TLPointerEventHandler = (info, e) => {
|
onPointerUp: TLPointerEventHandler = (info, e) => {
|
||||||
this.isPointing = false;
|
this.isPointing = false;
|
||||||
if (!this.shiftKey) this.isForcePanning = false;
|
|
||||||
this.updateInputs(info, e);
|
this.updateInputs(info, e);
|
||||||
this.currentTool.onPointerUp?.(info, e);
|
this.currentTool.onPointerUp?.(info, e);
|
||||||
};
|
};
|
||||||
@@ -4517,7 +4532,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
assets: {},
|
assets: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
static default_state: TDSnapshot = {
|
static defaultState: TDSnapshot = {
|
||||||
settings: {
|
settings: {
|
||||||
isCadSelectMode: false,
|
isCadSelectMode: false,
|
||||||
isPenMode: false,
|
isPenMode: false,
|
||||||
@@ -4527,6 +4542,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
isSnapping: false,
|
isSnapping: false,
|
||||||
isDebugMode: false,
|
isDebugMode: false,
|
||||||
isReadonlyMode: false,
|
isReadonlyMode: false,
|
||||||
|
forcePanning: false,
|
||||||
keepStyleMenuOpen: false,
|
keepStyleMenuOpen: false,
|
||||||
nudgeDistanceLarge: 16,
|
nudgeDistanceLarge: 16,
|
||||||
nudgeDistanceSmall: 1,
|
nudgeDistanceSmall: 1,
|
||||||
|
|||||||
@@ -18,34 +18,19 @@ export class HandDrawTool extends BaseTool {
|
|||||||
|
|
||||||
/* ----------------- Event Handlers ----------------- */
|
/* ----------------- Event Handlers ----------------- */
|
||||||
|
|
||||||
override onPointerDown: TLPointerEventHandler = () => {
|
override onEnter = () => {
|
||||||
if (this.app.readOnly) return;
|
this.app.patchState({
|
||||||
if (this.status !== Status.Idle) return;
|
settings: {
|
||||||
|
forcePanning: true,
|
||||||
this.set_status(Status.Pointing);
|
},
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
override onPointerMove: TLPointerEventHandler = (info, e) => {
|
override onExit = () => {
|
||||||
if (this.app.readOnly) return;
|
this.app.patchState({
|
||||||
const delta = Vec.div(info.delta, this.app.camera.zoom);
|
settings: {
|
||||||
const prev = this.app.camera.point;
|
forcePanning: false,
|
||||||
const next = Vec.sub(prev, delta);
|
},
|
||||||
if (Vec.isEqual(next, prev)) return;
|
});
|
||||||
|
|
||||||
switch (this.status) {
|
|
||||||
case Status.Pointing: {
|
|
||||||
this.app.pan(Vec.neg(delta));
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
override onPointerUp: TLPointerEventHandler = () => {
|
|
||||||
this.set_status(Status.Idle);
|
|
||||||
};
|
|
||||||
|
|
||||||
override onCancel = () => {
|
|
||||||
this.set_status(Status.Idle);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export interface TDSnapshot {
|
|||||||
isPenMode: boolean;
|
isPenMode: boolean;
|
||||||
isReadonlyMode: boolean;
|
isReadonlyMode: boolean;
|
||||||
isZoomSnap: boolean;
|
isZoomSnap: boolean;
|
||||||
|
forcePanning: boolean;
|
||||||
keepStyleMenuOpen: boolean;
|
keepStyleMenuOpen: boolean;
|
||||||
nudgeDistanceSmall: number;
|
nudgeDistanceSmall: number;
|
||||||
nudgeDistanceLarge: number;
|
nudgeDistanceLarge: number;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const StyledContainer = styled('div')({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
paddingLeft: '12px',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
background: '#f5f7f8',
|
background: '#f5f7f8',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
@@ -36,11 +37,6 @@ export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledContainer onClick={() => setOpen(prev => !prev)}>
|
<StyledContainer onClick={() => setOpen(prev => !prev)}>
|
||||||
{open ? (
|
|
||||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
|
||||||
) : (
|
|
||||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
|
||||||
)}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
color: '#98ACBD',
|
color: '#98ACBD',
|
||||||
|
|||||||
@@ -799,11 +799,11 @@ const EditorLeaf = ({ attributes, children, leaf }: any) => {
|
|||||||
<span {...attributes}>
|
<span {...attributes}>
|
||||||
<code
|
<code
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: 'rgba(135,131,120,0.15)',
|
backgroundColor: '#F2F5F9',
|
||||||
borderRadius: '3px',
|
borderRadius: '5px',
|
||||||
color: '#EB5757',
|
color: '#3A4C5C',
|
||||||
fontSize: '0.875em',
|
padding: '3px 8px',
|
||||||
padding: '0.25em 0.375em',
|
margin: '0 2px',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{customChildren}
|
{customChildren}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import { ReactEditor } from 'slate-react';
|
|||||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from '@mui/icons-material/Edit';
|
||||||
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
||||||
import AttachmentIcon from '@mui/icons-material/Attachment';
|
|
||||||
import {
|
import {
|
||||||
MuiTooltip as Tooltip,
|
MuiTooltip as Tooltip,
|
||||||
styled,
|
styled,
|
||||||
@@ -39,7 +38,7 @@ import {
|
|||||||
|
|
||||||
import { getRandomString } from '../utils';
|
import { getRandomString } from '../utils';
|
||||||
import { colors } from '../../colors';
|
import { colors } from '../../colors';
|
||||||
|
import { LinkIcon } from '@toeverything/components/icons';
|
||||||
export type LinkElement = {
|
export type LinkElement = {
|
||||||
type: 'link';
|
type: 'link';
|
||||||
url: string;
|
url: string;
|
||||||
@@ -124,7 +123,7 @@ const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
|||||||
))(({ theme }) => ({
|
))(({ theme }) => ({
|
||||||
[`& .${muiTooltipClasses.tooltip}`]: {
|
[`& .${muiTooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
color: '#4C6275',
|
color: '#3E6FDB',
|
||||||
boxShadow: theme.affine.shadows.shadow1,
|
boxShadow: theme.affine.shadows.shadow1,
|
||||||
fontSize: '14px',
|
fontSize: '14px',
|
||||||
},
|
},
|
||||||
@@ -419,8 +418,8 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className={styles('linkModalContainerIcon')}>
|
<div className={styles('linkModalContainerIcon')}>
|
||||||
<AttachmentIcon
|
<LinkIcon
|
||||||
style={{ color: colors.Gray04, fontSize: 16 }}
|
style={{ fontSize: '16px', marginTop: '2px' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
@@ -505,8 +504,10 @@ const LinkModalContainer = styled('div')(({ theme }) => ({
|
|||||||
|
|
||||||
const styles = style9.create({
|
const styles = style9.create({
|
||||||
linkModalContainerIcon: {
|
linkModalContainerIcon: {
|
||||||
|
display: 'flex',
|
||||||
width: '16px',
|
width: '16px',
|
||||||
margin: '0 16px 0 4px',
|
margin: '0 16px 0 4px',
|
||||||
|
color: '#4C6275',
|
||||||
},
|
},
|
||||||
linkModalContainerInput: {
|
linkModalContainerInput: {
|
||||||
flex: '1',
|
flex: '1',
|
||||||
@@ -517,6 +518,7 @@ const styles = style9.create({
|
|||||||
'::-webkit-input-placeholder': {
|
'::-webkit-input-placeholder': {
|
||||||
color: '#98acbd',
|
color: '#98acbd',
|
||||||
},
|
},
|
||||||
|
color: '#4C6275',
|
||||||
},
|
},
|
||||||
linkMask: {
|
linkMask: {
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
|||||||
@@ -905,6 +905,19 @@ class SlateUtils {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public insertNodes(
|
||||||
|
nodes: SlateNode | Array<SlateNode>,
|
||||||
|
options?: Parameters<typeof Transforms.insertNodes>[2]
|
||||||
|
) {
|
||||||
|
Transforms.insertNodes(this.editor, nodes, {
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodeByPath(path: Path) {
|
||||||
|
Editor.node(this.editor, path);
|
||||||
|
}
|
||||||
|
|
||||||
public getStartSelection() {
|
public getStartSelection() {
|
||||||
return {
|
return {
|
||||||
anchor: this.getStart(),
|
anchor: this.getStart(),
|
||||||
|
|||||||
@@ -40,10 +40,11 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
|||||||
(todoValue.length === 1 && !todoValue[0]['text'])
|
(todoValue.length === 1 && !todoValue[0]['text'])
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
const BulletLeft = styled('div')(() => ({
|
const BulletLeft = styled('div')(({ theme }) => ({
|
||||||
height: '22px',
|
height: '22px',
|
||||||
|
color: theme.affine.typography.body1.color,
|
||||||
}));
|
}));
|
||||||
export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
export const BulletView = ({ block, editor }: CreateView) => {
|
||||||
// block.remove();
|
// block.remove();
|
||||||
const properties = { ...defaultBulletProps, ...block.getProperties() };
|
const properties = { ...defaultBulletProps, ...block.getProperties() };
|
||||||
const textRef = useRef<ExtendedTextUtils>(null);
|
const textRef = useRef<ExtendedTextUtils>(null);
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ import {
|
|||||||
BlockPendantProvider,
|
BlockPendantProvider,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { copyToClipboard } from '@toeverything/utils';
|
import { copyToClipboard } from '@toeverything/utils';
|
||||||
|
import { DuplicateIcon } from '@toeverything/components/icons';
|
||||||
|
|
||||||
interface CreateCodeView extends CreateView {
|
interface CreateCodeView extends CreateView {
|
||||||
style9?: StyleWithAtRules;
|
style9?: StyleWithAtRules;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
@@ -117,16 +119,27 @@ const CodeBlock = styled('div')(({ theme }) => ({
|
|||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
},
|
},
|
||||||
'.copy-block': {
|
'.copy-block': {
|
||||||
padding: '6px 10px',
|
padding: '0px 10px',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
|
height: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
width: '90px',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
color: '#4C6275',
|
||||||
|
fontSize: '14px',
|
||||||
borderRadius: theme.affine.shape.borderRadius,
|
borderRadius: theme.affine.shape.borderRadius,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
svg: {
|
||||||
|
marginRight: '4px',
|
||||||
|
display: 'block',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'.cm-focused': {
|
'.cm-focused': {
|
||||||
outline: 'none !important',
|
outline: 'none !important',
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
export const CodeView = ({ block, editor }: CreateCodeView) => {
|
||||||
const initValue: string = block.getProperty('text')?.value?.[0]?.text;
|
const initValue: string = block.getProperty('text')?.value?.[0]?.text;
|
||||||
const langType: string = block.getProperty('lang');
|
const langType: string = block.getProperty('lang');
|
||||||
const [extensions, setExtensions] = useState<Extension[]>();
|
const [extensions, setExtensions] = useState<Extension[]>();
|
||||||
@@ -187,7 +200,7 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="copy-block" onClick={copyCode}>
|
<div className="copy-block" onClick={copyCode}>
|
||||||
Copy
|
<DuplicateIcon></DuplicateIcon>Copy
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ const Line = styled('div')({
|
|||||||
backgroundColor: '#e2e8f0',
|
backgroundColor: '#e2e8f0',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DividerView: FC<CreateView> = ({ block, editor }) => {
|
export const DividerView = ({ block, editor }: CreateView) => {
|
||||||
const [isSelected, setIsSelected] = useState(false);
|
const [isSelected, setIsSelected] = useState(false);
|
||||||
|
|
||||||
useOnSelect(block.id, (isSelect: boolean) => {
|
useOnSelect(block.id, (isSelect: boolean) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const MESSAGES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type EmbedLinkView = CreateView;
|
type EmbedLinkView = CreateView;
|
||||||
export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
export const EmbedLinkView = (props: EmbedLinkView) => {
|
||||||
const { block, editor } = props;
|
const { block, editor } = props;
|
||||||
const [isSelect, setIsSelect] = useState(false);
|
const [isSelect, setIsSelect] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const MESSAGES = {
|
|||||||
interface FigmaView extends CreateView {
|
interface FigmaView extends CreateView {
|
||||||
figmaUrl?: string;
|
figmaUrl?: string;
|
||||||
}
|
}
|
||||||
export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
export const FigmaView = ({ block, editor }: FigmaView) => {
|
||||||
const [figmaUrl, setFigmaUrl] = useState<string>(
|
const [figmaUrl, setFigmaUrl] = useState<string>(
|
||||||
block.getProperty('embedLink')?.value
|
block.getProperty('embedLink')?.value
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ const FileViewContainer = styled('div')<{ isSelected: boolean }>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
export const FileView: FC<FileView> = ({ block, editor }) => {
|
export const FileView = ({ block, editor }: FileView) => {
|
||||||
const [fileUrl, setFileUrl] = useState<string>();
|
const [fileUrl, setFileUrl] = useState<string>();
|
||||||
const fileInfo = block.getProperty('file') || ({} as FileColumnValue);
|
const fileInfo = block.getProperty('file') || ({} as FileColumnValue);
|
||||||
const file_id = fileInfo.value;
|
const file_id = fileInfo.value;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { GRID_PROPERTY_KEY, removePercent } from '../grid';
|
|||||||
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
export const GRID_ITEM_CLASS_NAME = 'grid-item';
|
||||||
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
export const GRID_ITEM_CONTENT_CLASS_NAME = `${GRID_ITEM_CLASS_NAME}-content`;
|
||||||
|
|
||||||
export const GridItem: FC<ChildrenView> = function (props) {
|
export const GridItem = function (props: ChildrenView) {
|
||||||
const { children, block, editor } = props;
|
const { children, block, editor } = props;
|
||||||
const RENDER_DELAY_TIME = 100;
|
const RENDER_DELAY_TIME = 100;
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { RenderBlock } from '@toeverything/components/editor-core';
|
|||||||
import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
|
import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
|
||||||
|
|
||||||
export const GridItemRender = function (creator: FC<ChildrenView>) {
|
export const GridItemRender = function (creator: FC<ChildrenView>) {
|
||||||
const GridItem: FC<CreateView> = function (props) {
|
const GridItem = function (props: CreateView) {
|
||||||
const { block } = props;
|
const { block } = props;
|
||||||
const children = (
|
const children = (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ type GridHandleProps = {
|
|||||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GridHandle: FC<GridHandleProps> = function ({
|
export const GridHandle = function ({
|
||||||
blockId,
|
blockId,
|
||||||
editor,
|
editor,
|
||||||
enabledAddItem,
|
enabledAddItem,
|
||||||
@@ -25,7 +25,7 @@ export const GridHandle: FC<GridHandleProps> = function ({
|
|||||||
draggable,
|
draggable,
|
||||||
alertHandleId,
|
alertHandleId,
|
||||||
onMouseEnter,
|
onMouseEnter,
|
||||||
}) {
|
}: GridHandleProps) {
|
||||||
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
|
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
|
||||||
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
|
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
|
||||||
if (draggable) {
|
if (draggable) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function removePercent(str: string) {
|
|||||||
return str.replace('%', '');
|
return str.replace('%', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Grid: FC<CreateView> = function (props) {
|
export const Grid = function (props: CreateView) {
|
||||||
const { block, editor } = props;
|
const { block, editor } = props;
|
||||||
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||||
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
||||||
@@ -261,7 +261,7 @@ const GridContainer = styled('div')<{
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'stretch',
|
alignItems: 'stretch',
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
border: '1px solid #FFF',
|
border: '1px solid transparent',
|
||||||
minWidth: `${gridItemMinWidth}%`,
|
minWidth: `${gridItemMinWidth}%`,
|
||||||
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||||
borderColor: theme.affine.palette.borderColor,
|
borderColor: theme.affine.palette.borderColor,
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ export class GridBlock extends BaseView {
|
|||||||
}
|
}
|
||||||
return block.remove();
|
return block.remove();
|
||||||
}
|
}
|
||||||
|
if (block.childrenIds.length === 0) {
|
||||||
|
return block.remove();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
addNewGroup,
|
addNewGroup,
|
||||||
|
LINE_GAP,
|
||||||
RecastScene,
|
RecastScene,
|
||||||
|
TAG_GAP,
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
@@ -38,6 +40,7 @@ const GroupActionWrapper = styled('div')(({ theme }) => ({
|
|||||||
visibility: 'hidden',
|
visibility: 'hidden',
|
||||||
fontSize: theme.affine.typography.xs.fontSize,
|
fontSize: theme.affine.typography.xs.fontSize,
|
||||||
color: theme.affine.palette.icons,
|
color: theme.affine.palette.icons,
|
||||||
|
opacity: 0.6,
|
||||||
'.line': {
|
'.line': {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
height: '15px',
|
height: '15px',
|
||||||
@@ -60,7 +63,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
|||||||
({ isSelect, theme }) => ({
|
({ isSelect, theme }) => ({
|
||||||
background: theme.affine.palette.white,
|
background: theme.affine.palette.white,
|
||||||
border: '2px solid rgba(236,241,251,.5)',
|
border: '2px solid rgba(236,241,251,.5)',
|
||||||
padding: `15px 16px 0 16px`,
|
padding: `15px 16px ${LINE_GAP - TAG_GAP * 2}px 16px`,
|
||||||
borderRadius: '10px',
|
borderRadius: '10px',
|
||||||
...(isSelect
|
...(isSelect
|
||||||
? {
|
? {
|
||||||
@@ -75,7 +78,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
export const GroupView: FC<CreateView> = props => {
|
export const GroupView = (props: CreateView) => {
|
||||||
const { block, editor } = props;
|
const { block, editor } = props;
|
||||||
const [currentView] = useCurrentView();
|
const [currentView] = useCurrentView();
|
||||||
const [groupIsSelect, setGroupIsSelect] = useState(false);
|
const [groupIsSelect, setGroupIsSelect] = useState(false);
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ import { RenderBlockChildren } from '@toeverything/components/editor-core';
|
|||||||
import type { CreateView } from '@toeverything/framework/virgo';
|
import type { CreateView } from '@toeverything/framework/virgo';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
|
||||||
export const ScenePage: FC<CreateView> = ({ block }) => {
|
export const ScenePage = ({ block }: CreateView) => {
|
||||||
return <RenderBlockChildren block={block} />;
|
return <RenderBlockChildren block={block} />;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import type { CreateView } from '@toeverything/framework/virgo';
|
|||||||
import type { TableColumn, TableRow } from '../../components/table';
|
import type { TableColumn, TableRow } from '../../components/table';
|
||||||
import { Table, CustomCell } from '../../components/table';
|
import { Table, CustomCell } from '../../components/table';
|
||||||
|
|
||||||
export const SceneTable: FC<CreateView> = ({ block, columns, editor }) => {
|
export const SceneTable = ({ block, columns, editor }: CreateView) => {
|
||||||
const [rows, set_rows] = useState<TableRow[]>([]);
|
const [rows, set_rows] = useState<TableRow[]>([]);
|
||||||
const data_columns = useMemo<TableColumn[]>(() => {
|
const data_columns = useMemo<TableColumn[]>(() => {
|
||||||
return (columns || [])
|
return (columns || [])
|
||||||
|
|||||||
@@ -156,7 +156,13 @@ export const CardContainer = (props: CardContainerProps) => {
|
|||||||
const { kanban } = useKanban();
|
const { kanban } = useKanban();
|
||||||
const { containerIds, items: dataSource, activeId } = props;
|
const { containerIds, items: dataSource, activeId } = props;
|
||||||
return (
|
return (
|
||||||
<KanbanContainer>
|
<KanbanContainer
|
||||||
|
onMouseDown={e => {
|
||||||
|
// Fix https://github.com/toeverything/AFFiNE/issues/29
|
||||||
|
// Prevent active selection when dragging kanban card
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
>
|
||||||
{containerIds.map((containerId, idx) => {
|
{containerIds.map((containerId, idx) => {
|
||||||
const items = dataSource[containerId];
|
const items = dataSource[containerId];
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,9 @@ export const CardContext = (props: Props) => {
|
|||||||
|
|
||||||
const StyledCardContainer = styled('div')`
|
const StyledCardContainer = styled('div')`
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { CreateView } from '@toeverything/framework/virgo';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
|
|
||||||
export const GroupDividerView: FC<CreateView> = ({ block, editor }) => {
|
export const GroupDividerView = ({ block, editor }: CreateView) => {
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const KanbanImageContainer = styled('div')<{ isSelected: boolean }>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
export const ImageView = ({ block, editor }: ImageView) => {
|
||||||
const workspace = editor.workspace;
|
const workspace = editor.workspace;
|
||||||
const [imgUrl, set_image_url] = useState<string>();
|
const [imgUrl, set_image_url] = useState<string>();
|
||||||
const [imgWidth, setImgWidth] = useState<number>(0);
|
const [imgWidth, setImgWidth] = useState<number>(0);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const NumberedView: FC<CreateView> = ({ block, editor }) => {
|
export const NumberedView = ({ block, editor }: CreateView) => {
|
||||||
// block.remove();
|
// block.remove();
|
||||||
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
||||||
const [number, set_number] = useState<number>(1);
|
const [number, set_number] = useState<number>(1);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
type ExtendedTextUtils,
|
type ExtendedTextUtils,
|
||||||
} from '../../components/text-manage';
|
} from '../../components/text-manage';
|
||||||
|
|
||||||
export const PageView: FC<CreateView> = ({ block, editor }) => {
|
export const PageView = ({ block, editor }: CreateView) => {
|
||||||
const { workspace_id } = useParams();
|
const { workspace_id } = useParams();
|
||||||
const textRef = useRef<ExtendedTextUtils>(null);
|
const textRef = useRef<ExtendedTextUtils>(null);
|
||||||
const [backLinks, setBackLinks] =
|
const [backLinks, setBackLinks] =
|
||||||
@@ -109,12 +109,15 @@ export const PageView: FC<CreateView> = ({ block, editor }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const PageTitleBlock = styled('div')({
|
const PageTitleBlock = styled('div')(({ theme }) => {
|
||||||
'.title': {
|
return {
|
||||||
fontSize: Theme.typography.page.fontSize,
|
'.title': {
|
||||||
lineHeight: Theme.typography.page.lineHeight,
|
fontSize: theme.affine.typography.page.fontSize,
|
||||||
},
|
lineHeight: theme.affine.typography.page.lineHeight,
|
||||||
'.content': {
|
fontWeight: theme.affine.typography.page.fontWeight,
|
||||||
outline: 'none',
|
},
|
||||||
},
|
'.content': {
|
||||||
|
outline: 'none',
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { CreateView } from '@toeverything/framework/virgo';
|
|||||||
|
|
||||||
type RefLinkView = CreateView;
|
type RefLinkView = CreateView;
|
||||||
|
|
||||||
export const RefLinkView: FC<RefLinkView> = ({ block, editor }) => {
|
export const RefLinkView = ({ block, editor }: RefLinkView) => {
|
||||||
const page_id = useMemo(() => block.getProperty('reference'), [block]);
|
const page_id = useMemo(() => block.getProperty('reference'), [block]);
|
||||||
|
|
||||||
const [block_content, set_block] =
|
const [block_content, set_block] =
|
||||||
|
|||||||
@@ -46,15 +46,16 @@ const TextBlock = styled(TextManage)<{ type: string }>(({ theme, type }) => {
|
|||||||
return {
|
return {
|
||||||
fontSize: textStyleMap.text.fontSize,
|
fontSize: textStyleMap.text.fontSize,
|
||||||
lineHeight: textStyleMap.text.lineHeight,
|
lineHeight: textStyleMap.text.lineHeight,
|
||||||
|
fontWeight: textStyleMap.text.fontWeight,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const TextView: FC<CreateTextView> = ({
|
export const TextView = ({
|
||||||
block,
|
block,
|
||||||
editor,
|
editor,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
}) => {
|
}: CreateTextView) => {
|
||||||
const [isSelect, setIsSelect] = useState<boolean>();
|
const [isSelect, setIsSelect] = useState<boolean>();
|
||||||
useOnSelect(block.id, (is_select: boolean) => {
|
useOnSelect(block.id, (is_select: boolean) => {
|
||||||
setIsSelect(is_select);
|
setIsSelect(is_select);
|
||||||
@@ -112,13 +113,18 @@ export const TextView: FC<CreateTextView> = ({
|
|||||||
block.id,
|
block.id,
|
||||||
'end'
|
'end'
|
||||||
);
|
);
|
||||||
const value = [
|
if (
|
||||||
...preNode.getProperty('text').value,
|
block.getProperty('text').value[0] &&
|
||||||
...block.getProperty('text').value,
|
block.getProperty('text').value[0]?.text !== ''
|
||||||
];
|
) {
|
||||||
await preNode.setProperty('text', {
|
const value = [
|
||||||
value,
|
...preNode.getProperty('text').value,
|
||||||
});
|
...block.getProperty('text').value,
|
||||||
|
];
|
||||||
|
await preNode.setProperty('text', {
|
||||||
|
value,
|
||||||
|
});
|
||||||
|
}
|
||||||
await preNode.append(...children);
|
await preNode.append(...children);
|
||||||
await block.remove();
|
await block.remove();
|
||||||
editor.suspend(false);
|
editor.suspend(false);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const INITIAL_LIST: MenuItem[] = [];
|
|||||||
const MESSAGES = {
|
const MESSAGES = {
|
||||||
NO_HEADINGS_FOUND: 'No headings found',
|
NO_HEADINGS_FOUND: 'No headings found',
|
||||||
};
|
};
|
||||||
export const TocView: FC<CreateView> = ({ block, editor }) => {
|
export const TocView = ({ block, editor }: CreateView) => {
|
||||||
const [list, setList] = useState(INITIAL_LIST);
|
const [list, setList] = useState(INITIAL_LIST);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const sync_toc = async () => {
|
const sync_toc = async () => {
|
||||||
|
|||||||
@@ -8,20 +8,20 @@ interface CheckBoxProps {
|
|||||||
onChange: (checked: boolean) => void;
|
onChange: (checked: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CheckBox: FC<CheckBoxProps> = ({
|
export const CheckBox = ({
|
||||||
size = '16px',
|
size = 16,
|
||||||
height = '23px',
|
height = 23,
|
||||||
checked,
|
checked,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}: CheckBoxProps) => {
|
||||||
const dynamic_style = useMemo(
|
const dynamic_style = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
height: {
|
height: {
|
||||||
height,
|
height: `${height}px`,
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
width: size,
|
width: `${size}px`,
|
||||||
height: size,
|
height: `${size}px`,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[height, size]
|
[height, size]
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TodoView: FC<CreateView> = ({ block, editor }) => {
|
export const TodoView = ({ block, editor }: CreateView) => {
|
||||||
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
||||||
const text_ref = useRef<ExtendedTextUtils>(null);
|
const text_ref = useRef<ExtendedTextUtils>(null);
|
||||||
|
|
||||||
@@ -150,6 +150,7 @@ const TodoBlock = styled('div')({
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
'.checkBoxContainer': {
|
'.checkBoxContainer': {
|
||||||
marginRight: '4px',
|
marginRight: '4px',
|
||||||
|
padding: '0 4px',
|
||||||
height: '22px',
|
height: '22px',
|
||||||
},
|
},
|
||||||
'.textContainer': {
|
'.textContainer': {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const _messages = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type YoutubeView = CreateView;
|
type YoutubeView = CreateView;
|
||||||
export const YoutubeView: FC<YoutubeView> = ({ block }) => {
|
export const YoutubeView = ({ block }: YoutubeView) => {
|
||||||
const [youtubeUrl, setYoutubeUrl] = useState<string>(
|
const [youtubeUrl, setYoutubeUrl] = useState<string>(
|
||||||
block.getProperty('embedLink')?.value
|
block.getProperty('embedLink')?.value
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ type BlockContainerProps = Parameters<typeof Container>[0] & {
|
|||||||
editor: BlockEditor;
|
editor: BlockEditor;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BlockContainer: FC<BlockContainerProps> = function ({
|
export const BlockContainer = function ({
|
||||||
block,
|
block,
|
||||||
children,
|
children,
|
||||||
className,
|
className,
|
||||||
editor,
|
editor,
|
||||||
...restProps
|
...restProps
|
||||||
}) {
|
}: BlockContainerProps) {
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
className={`${className || ''} block_container`}
|
className={`${className || ''} block_container`}
|
||||||
@@ -27,5 +27,6 @@ export const BlockContainer: FC<BlockContainerProps> = function ({
|
|||||||
export const Container = styled('div')<{ selected: boolean }>(
|
export const Container = styled('div')<{ selected: boolean }>(
|
||||||
({ selected, theme }) => ({
|
({ selected, theme }) => ({
|
||||||
backgroundColor: selected ? theme.affine.palette.textSelected : '',
|
backgroundColor: selected ? theme.affine.palette.textSelected : '',
|
||||||
|
marginBottom: '2px',
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const ImageContainer = styled('div')<{ isSelected: boolean }>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
export const Image: FC<Props> = props => {
|
export const Image = (props: Props) => {
|
||||||
const { link, viewStyle, isSelected, block } = props;
|
const { link, viewStyle, isSelected, block } = props;
|
||||||
const on_resize_end = (e: any, data: any) => {
|
const on_resize_end = (e: any, data: any) => {
|
||||||
block.setProperty('image_style', data.size);
|
block.setProperty('image_style', data.size);
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { styled } from '@toeverything/components/ui';
|
|||||||
/**
|
/**
|
||||||
* Indent rendering child nodes
|
* Indent rendering child nodes
|
||||||
*/
|
*/
|
||||||
export const IndentWrapper: FC<PropsWithChildren> = props => {
|
export const IndentWrapper = (props: PropsWithChildren) => {
|
||||||
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
|
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { ErrorBoundary } from '@toeverything/utils';
|
|||||||
// onChange: () => void;
|
// onChange: () => void;
|
||||||
// }
|
// }
|
||||||
|
|
||||||
export const Editable: FC = () => {
|
export const Editable = () => {
|
||||||
const editor = useMemo(() => withReact(createEditor()), []);
|
const editor = useMemo(() => withReact(createEditor()), []);
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary
|
<ErrorBoundary
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ const LoadingContiner = () => {
|
|||||||
return <Loading>loading...</Loading>;
|
return <Loading>loading...</Loading>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SourceView: FC<Props> = props => {
|
export const SourceView = (props: Props) => {
|
||||||
const { link, isSelected, block, editorElement } = props;
|
const { link, isSelected, block, editorElement } = props;
|
||||||
const src = formatUrl(link);
|
const src = formatUrl(link);
|
||||||
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const List = styled('div')(({ theme }) => ({
|
|||||||
'.checkBoxContainer': {
|
'.checkBoxContainer': {
|
||||||
marginRight: '4px',
|
marginRight: '4px',
|
||||||
lineHeight: theme.affine.typography.body1.lineHeight,
|
lineHeight: theme.affine.typography.body1.lineHeight,
|
||||||
|
color: theme.affine.typography.body1.color,
|
||||||
},
|
},
|
||||||
'.textContainer': {
|
'.textContainer': {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
|
|||||||
@@ -64,8 +64,13 @@ export const DEFAULT_RENDER_CELL: CustomCell = ({ value }) => {
|
|||||||
return <span>{value ? String(value) : '--'}</span>;
|
return <span>{value ? String(value) : '--'}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Cell: FC<GridChildComponentProps<TableData>> = memo(
|
const Cell = memo(
|
||||||
({ data, rowIndex, columnIndex, style }) => {
|
({
|
||||||
|
data,
|
||||||
|
rowIndex,
|
||||||
|
columnIndex,
|
||||||
|
style,
|
||||||
|
}: GridChildComponentProps<TableData>) => {
|
||||||
const column = data.columns[columnIndex];
|
const column = data.columns[columnIndex];
|
||||||
const row = data.rows[rowIndex];
|
const row = data.rows[rowIndex];
|
||||||
const is_first_column = columnIndex === 0;
|
const is_first_column = columnIndex === 0;
|
||||||
@@ -89,18 +94,17 @@ const Cell: FC<GridChildComponentProps<TableData>> = memo(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
areEqual
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export const BasicTable: FC<BasicTableProps> = ({
|
export const BasicTable = ({
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
headerHeight = DEFAULT_ROW_HEIGHT,
|
headerHeight = DEFAULT_ROW_HEIGHT,
|
||||||
rowKey,
|
rowKey,
|
||||||
border = true,
|
border = true,
|
||||||
renderCell = DEFAULT_RENDER_CELL,
|
renderCell = DEFAULT_RENDER_CELL,
|
||||||
}) => {
|
}: BasicTableProps) => {
|
||||||
const container_ref = useRef<HTMLDivElement>();
|
const container_ref = useRef<HTMLDivElement>();
|
||||||
const [table_width, set_table_width] = useState(0);
|
const [table_width, set_table_width] = useState(0);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import type { CellProps } from '../types';
|
|||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export const CheckBoxCell: FC<CellProps<BooleanColumnValue>> = ({
|
export const CheckBoxCell = ({
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}: CellProps<BooleanColumnValue>) => {
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={value?.value}
|
checked={value?.value}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import type { CellProps } from './types';
|
|||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
const DefaultCell: FC<CellProps> = ({ onChange, ...props }) => {
|
const DefaultCell = ({ onChange, ...props }: CellProps) => {
|
||||||
return <DEFAULT_RENDER_CELL {...props} />;
|
return <DEFAULT_RENDER_CELL {...props} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ interface CustomCellProps extends TableCustomCellProps<unknown> {
|
|||||||
onChange: (data: TableCustomCellProps<unknown>) => void;
|
onChange: (data: TableCustomCellProps<unknown>) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CustomCell: FC<CustomCellProps> = props => {
|
export const CustomCell = (props: CustomCellProps) => {
|
||||||
const View =
|
const View =
|
||||||
props.rowIndex === 0
|
props.rowIndex === 0
|
||||||
? DefaultCell
|
? DefaultCell
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import type { CellProps } from '../types';
|
|||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
export const SelectCell: FC<CellProps<EnumColumnValue>> = ({
|
export const SelectCell = ({
|
||||||
value,
|
value,
|
||||||
column,
|
column,
|
||||||
onChange,
|
onChange,
|
||||||
}) => {
|
}: CellProps<EnumColumnValue>) => {
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
if (isEnumColumn(column.columnConfig)) {
|
if (isEnumColumn(column.columnConfig)) {
|
||||||
return column.columnConfig.options.map(option => {
|
return column.columnConfig.options.map(option => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ interface TableProps extends BasicTableProps {
|
|||||||
addon?: ReactNode;
|
addon?: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Table: FC<TableProps> = ({ addon, ...props }) => {
|
export const Table = ({ addon, ...props }: TableProps) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{addon}
|
{addon}
|
||||||
|
|||||||
@@ -39,16 +39,22 @@ export type ExtendedTextUtils = SlateUtils & {
|
|||||||
};
|
};
|
||||||
const TextBlockContainer = styled(Text)(({ theme }) => ({
|
const TextBlockContainer = styled(Text)(({ theme }) => ({
|
||||||
lineHeight: theme.affine.typography.body1.lineHeight,
|
lineHeight: theme.affine.typography.body1.lineHeight,
|
||||||
|
fontFamily: theme.affine.typography.body1.fontFamily,
|
||||||
|
color: theme.affine.typography.body1.color,
|
||||||
|
letterSpacing: '0.1px',
|
||||||
|
a: {
|
||||||
|
color: '#3e6fdb',
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const findSlice = (arr: string[], p: string, q: string) => {
|
const findSlice = (arr: string[], p: string, q: string) => {
|
||||||
let should_include = false;
|
let shouldInclude = false;
|
||||||
return arr.filter(block => {
|
return arr.filter(block => {
|
||||||
if (block === p || block === q) {
|
if (block === p || block === q) {
|
||||||
should_include = !should_include;
|
shouldInclude = !shouldInclude;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return should_include;
|
return shouldInclude;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -112,11 +118,8 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
(ref as MutableRefObject<ExtendedTextUtils>) || defaultRef;
|
(ref as MutableRefObject<ExtendedTextUtils>) || defaultRef;
|
||||||
|
|
||||||
const properties = block.getProperties();
|
const properties = block.getProperties();
|
||||||
// const [is_select, set_is_select] = useState<boolean>();
|
|
||||||
// useOnSelect(block.id, (is_select: boolean) => {
|
const onTextViewSetSelection = (selection: Range | Point) => {
|
||||||
// set_is_select(is_select);
|
|
||||||
// });
|
|
||||||
const on_text_view_set_selection = (selection: Range | Point) => {
|
|
||||||
if (selection instanceof Point) {
|
if (selection instanceof Point) {
|
||||||
//do some thing
|
//do some thing
|
||||||
} else {
|
} else {
|
||||||
@@ -125,18 +128,18 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
||||||
const on_text_view_active = useCallback(
|
const onTextViewActive = useCallback(
|
||||||
(point: CursorTypes) => {
|
(point: CursorTypes) => {
|
||||||
// TODO code to be optimized
|
// TODO code to be optimized
|
||||||
if (textRef.current) {
|
if (textRef.current) {
|
||||||
const end_selection = textRef.current.getEndSelection();
|
const endSelection = textRef.current.getEndSelection();
|
||||||
const start_selection = textRef.current.getStartSelection();
|
const startSelection = textRef.current.getStartSelection();
|
||||||
if (point === 'start') {
|
if (point === 'start') {
|
||||||
textRef.current.setSelection(start_selection);
|
textRef.current.setSelection(startSelection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (point === 'end') {
|
if (point === 'end') {
|
||||||
textRef.current.setSelection(end_selection);
|
textRef.current.setSelection(endSelection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -151,24 +154,24 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
} else {
|
} else {
|
||||||
blockTop = blockDomStyle.top + 5;
|
blockTop = blockDomStyle.top + 5;
|
||||||
}
|
}
|
||||||
const end_position = ReactEditor.toDOMRange(
|
const endPosition = ReactEditor.toDOMRange(
|
||||||
textRef.current.editor,
|
textRef.current.editor,
|
||||||
end_selection
|
endSelection
|
||||||
)
|
)
|
||||||
.getClientRects()
|
.getClientRects()
|
||||||
.item(0);
|
.item(0);
|
||||||
const start_position = ReactEditor.toDOMRange(
|
const startPosition = ReactEditor.toDOMRange(
|
||||||
textRef.current.editor,
|
textRef.current.editor,
|
||||||
start_selection
|
startSelection
|
||||||
)
|
)
|
||||||
.getClientRects()
|
.getClientRects()
|
||||||
.item(0);
|
.item(0);
|
||||||
if (end_position.left <= point.x) {
|
if (endPosition.left <= point.x) {
|
||||||
textRef.current.setSelection(end_selection);
|
textRef.current.setSelection(endSelection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (start_position.left >= point.x) {
|
if (startPosition.left >= point.x) {
|
||||||
textRef.current.setSelection(start_selection);
|
textRef.current.setSelection(startSelection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let range: globalThis.Range;
|
let range: globalThis.Range;
|
||||||
@@ -186,7 +189,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
range = document.createRange();
|
range = document.createRange();
|
||||||
range.setStart(caret.offsetNode, caret.offset);
|
range.setStart(caret.offsetNode, caret.offset);
|
||||||
}
|
}
|
||||||
const slate_rang = ReactEditor.toSlateRange(
|
const slateRang = ReactEditor.toSlateRange(
|
||||||
textRef.current.editor,
|
textRef.current.editor,
|
||||||
range,
|
range,
|
||||||
{
|
{
|
||||||
@@ -194,19 +197,19 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
suppressThrow: true,
|
suppressThrow: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
textRef.current.setSelection(slate_rang);
|
textRef.current.setSelection(slateRang);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('e: ', e);
|
console.log('e: ', e);
|
||||||
textRef.current.setSelection(end_selection);
|
textRef.current.setSelection(endSelection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[textRef]
|
[textRef]
|
||||||
);
|
);
|
||||||
|
|
||||||
useOnSelectActive(block.id, on_text_view_active);
|
useOnSelectActive(block.id, onTextViewActive);
|
||||||
useOnSelectSetSelection<'Range'>(block.id, on_text_view_set_selection);
|
useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (textRef.current) {
|
if (textRef.current) {
|
||||||
@@ -232,17 +235,17 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
(block.id === lastSelectNodeId && type === 'Range') ||
|
(block.id === lastSelectNodeId && type === 'Range') ||
|
||||||
(type === 'Range' && info)
|
(type === 'Range' && info)
|
||||||
) {
|
) {
|
||||||
on_text_view_active('end');
|
onTextViewActive('end');
|
||||||
} else {
|
} else {
|
||||||
on_text_view_active('start');
|
onTextViewActive('start');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('error occured in set active in initialization');
|
console.warn('error occured in set active in initialization');
|
||||||
}
|
}
|
||||||
}, [block.id, editor.selectionManager, on_text_view_active, textRef]);
|
}, [block.id, editor.selectionManager, onTextViewActive, textRef]);
|
||||||
|
|
||||||
const on_text_change: TextProps['handleChange'] = async (
|
const onTextChange: TextProps['handleChange'] = async (
|
||||||
value,
|
value,
|
||||||
textStyle
|
textStyle
|
||||||
) => {
|
) => {
|
||||||
@@ -263,39 +266,34 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const get_now_and_pre_rang_position = () => {
|
const getNowAndPreRangPosition = () => {
|
||||||
window.getSelection().getRangeAt(0);
|
const nowRange = window.getSelection().getRangeAt(0);
|
||||||
// const now_range =
|
let prePosition = null;
|
||||||
// editor.selectionManager.currentSelectInfo?.browserSelection.getRangeAt(
|
const nowPosition = nowRange.getClientRects().item(0);
|
||||||
// 0
|
|
||||||
// );
|
|
||||||
const now_range = window.getSelection().getRangeAt(0);
|
|
||||||
let pre_position = null;
|
|
||||||
const now_position = now_range.getClientRects().item(0);
|
|
||||||
try {
|
try {
|
||||||
if (now_range.startOffset !== 0) {
|
if (nowRange.startOffset !== 0) {
|
||||||
const pre_rang = document.createRange();
|
const preRang = document.createRange();
|
||||||
pre_rang.setStart(
|
preRang.setStart(
|
||||||
now_range.startContainer,
|
nowRange.startContainer,
|
||||||
now_range.startOffset + 1
|
nowRange.startOffset + 1
|
||||||
);
|
);
|
||||||
pre_rang.setEnd(
|
preRang.setEnd(
|
||||||
now_range.endContainer,
|
nowRange.endContainer,
|
||||||
now_range.endOffset + 1
|
nowRange.endOffset + 1
|
||||||
);
|
);
|
||||||
pre_position = pre_rang.getClientRects().item(0);
|
prePosition = preRang.getClientRects().item(0);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e);
|
// console.log(e);
|
||||||
}
|
}
|
||||||
return { nowPosition: now_position, prePosition: pre_position };
|
return { nowPosition: nowPosition, prePosition: prePosition };
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyboardUp = (event: React.KeyboardEvent<Element>) => {
|
const onKeyboardUp = (event: React.KeyboardEvent<Element>) => {
|
||||||
// if default event is prevented do noting
|
// if default event is prevented do noting
|
||||||
// if U want to disable up/down/enter use capture event for preventing
|
// if U want to disable up/down/enter use capture event for preventing
|
||||||
if (!event.isDefaultPrevented()) {
|
if (!event.isDefaultPrevented()) {
|
||||||
const positions = get_now_and_pre_rang_position();
|
const positions = getNowAndPreRangPosition();
|
||||||
const prePosition = positions.prePosition;
|
const prePosition = positions.prePosition;
|
||||||
const nowPosition = positions.nowPosition;
|
const nowPosition = positions.nowPosition;
|
||||||
if (prePosition) {
|
if (prePosition) {
|
||||||
@@ -336,7 +334,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
// editor.selectionManager.activeNextNode(block.id, 'start');
|
// editor.selectionManager.activeNextNode(block.id, 'start');
|
||||||
// return;
|
// return;
|
||||||
if (!event.isDefaultPrevented()) {
|
if (!event.isDefaultPrevented()) {
|
||||||
const positions = get_now_and_pre_rang_position();
|
const positions = getNowAndPreRangPosition();
|
||||||
const prePosition = positions.prePosition;
|
const prePosition = positions.prePosition;
|
||||||
const nowPosition = positions.nowPosition;
|
const nowPosition = positions.nowPosition;
|
||||||
// Create the last element range of slate_editor
|
// Create the last element range of slate_editor
|
||||||
@@ -392,7 +390,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const on_select_all = () => {
|
const onSelectAll = () => {
|
||||||
const isSelectAll =
|
const isSelectAll =
|
||||||
textRef.current.isEmpty() || textRef.current.isSelectAll();
|
textRef.current.isEmpty() || textRef.current.isSelectAll();
|
||||||
if (isSelectAll) {
|
if (isSelectAll) {
|
||||||
@@ -402,22 +400,20 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
const on_undo = () => {
|
const onUndo = () => {
|
||||||
editor.undo();
|
editor.undo();
|
||||||
};
|
};
|
||||||
|
|
||||||
const on_redo = () => {
|
const onRedo = () => {
|
||||||
editor.redo();
|
editor.redo();
|
||||||
};
|
};
|
||||||
|
|
||||||
const on_keyboard_esc = () => {
|
const onKeyboardEsc = () => {
|
||||||
if (editor.selectionManager.getSelectedNodesIds().length === 0) {
|
if (editor.selectionManager.getSelectedNodesIds().length === 0) {
|
||||||
const active_node_id =
|
const activeNodeId =
|
||||||
editor.selectionManager.getActivatedNodeId();
|
editor.selectionManager.getActivatedNodeId();
|
||||||
if (active_node_id) {
|
if (activeNodeId) {
|
||||||
editor.selectionManager.setSelectedNodesIds([
|
editor.selectionManager.setSelectedNodesIds([activeNodeId]);
|
||||||
active_node_id,
|
|
||||||
]);
|
|
||||||
ReactEditor.blur(textRef.current.editor);
|
ReactEditor.blur(textRef.current.editor);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -425,7 +421,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const on_shift_click = async (e: MouseEvent) => {
|
const onShiftClick = async (e: MouseEvent) => {
|
||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
const activeId = editor.selectionManager.getActivatedNodeId();
|
const activeId = editor.selectionManager.getActivatedNodeId();
|
||||||
if (activeId === block.id) {
|
if (activeId === block.id) {
|
||||||
@@ -474,16 +470,16 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
|||||||
className={`${otherOptions.className}`}
|
className={`${otherOptions.className}`}
|
||||||
currentValue={properties.text.value}
|
currentValue={properties.text.value}
|
||||||
textStyle={properties.textStyle}
|
textStyle={properties.textStyle}
|
||||||
handleChange={on_text_change}
|
handleChange={onTextChange}
|
||||||
handleUp={onKeyboardUp}
|
handleUp={onKeyboardUp}
|
||||||
handleDown={onKeyboardDown}
|
handleDown={onKeyboardDown}
|
||||||
handleLeft={onKeyboardLeft}
|
handleLeft={onKeyboardLeft}
|
||||||
handleRight={onKeyboardRight}
|
handleRight={onKeyboardRight}
|
||||||
handleSelectAll={on_select_all}
|
handleSelectAll={onSelectAll}
|
||||||
handleMouseDown={on_shift_click}
|
handleMouseDown={onShiftClick}
|
||||||
handleUndo={on_undo}
|
handleUndo={onUndo}
|
||||||
handleRedo={on_redo}
|
handleRedo={onRedo}
|
||||||
handleEsc={on_keyboard_esc}
|
handleEsc={onKeyboardEsc}
|
||||||
{...otherOptions}
|
{...otherOptions}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ const UploadBox = styled('div')<{ isSelected: boolean }>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const button_styles: SxProps = { width: '60%', fontSize: '12px' };
|
const button_styles: SxProps = { width: '60%', fontSize: '12px' };
|
||||||
export const Upload: FC<Props> = props => {
|
export const Upload = (props: Props) => {
|
||||||
const {
|
const {
|
||||||
fileChange,
|
fileChange,
|
||||||
size,
|
size,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ const defaultConfig: WithChildrenConfig = {
|
|||||||
const TreeView = forwardRef<
|
const TreeView = forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
||||||
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
>(({ lastItem = false, children, onClick, ...restProps }, ref) => {
|
||||||
return (
|
return (
|
||||||
<TreeWrapper ref={ref} {...restProps}>
|
<TreeWrapper ref={ref} {...restProps}>
|
||||||
<StyledTreeView>
|
<StyledTreeView>
|
||||||
@@ -155,8 +155,11 @@ const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
|
|||||||
const Children = Wrapper;
|
const Children = Wrapper;
|
||||||
|
|
||||||
const TREE_COLOR = '#D5DFE6';
|
const TREE_COLOR = '#D5DFE6';
|
||||||
// TODO determine the position of the horizontal line by the type of the item
|
// adjust left and right margins of the the tree line
|
||||||
const ITEM_POINT_HEIGHT = '12.5px'; // '50%'
|
const TREE_LINE_LEFT_OFFSET = '-16px';
|
||||||
|
// determine the position of the horizontal line by the type of the item
|
||||||
|
const TREE_LINE_TOP_OFFSET = '20px'; // '50%'
|
||||||
|
const TREE_LINE_WIDTH = '12px';
|
||||||
|
|
||||||
const TreeWrapper = styled('div')({
|
const TreeWrapper = styled('div')({
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
@@ -164,7 +167,7 @@ const TreeWrapper = styled('div')({
|
|||||||
|
|
||||||
const StyledTreeView = styled('div')({
|
const StyledTreeView = styled('div')({
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '-21px',
|
left: TREE_LINE_LEFT_OFFSET,
|
||||||
height: '100%',
|
height: '100%',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -183,7 +186,7 @@ const Line = styled('div')({
|
|||||||
|
|
||||||
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
width: '1px',
|
width: '1px',
|
||||||
height: last ? ITEM_POINT_HEIGHT : '100%',
|
height: last ? TREE_LINE_TOP_OFFSET : '100%',
|
||||||
paddingTop: 0,
|
paddingTop: 0,
|
||||||
paddingBottom: 0,
|
paddingBottom: 0,
|
||||||
transform: 'translate(-50%, 0)',
|
transform: 'translate(-50%, 0)',
|
||||||
@@ -192,11 +195,11 @@ const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||||
width: '16px',
|
width: TREE_LINE_WIDTH,
|
||||||
height: '1px',
|
height: '1px',
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
paddingRight: 0,
|
paddingRight: 0,
|
||||||
top: ITEM_POINT_HEIGHT,
|
top: TREE_LINE_TOP_OFFSET,
|
||||||
transform: 'translate(0, -50%)',
|
transform: 'translate(0, -50%)',
|
||||||
opacity: last ? 0 : 'unset',
|
opacity: last ? 0 : 'unset',
|
||||||
}));
|
}));
|
||||||
@@ -204,7 +207,8 @@ const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
|||||||
const Collapsed = styled('div')({
|
const Collapsed = styled('div')({
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
color: '#B9CAD5',
|
color: '#98ACBD',
|
||||||
|
padding: '8px',
|
||||||
});
|
});
|
||||||
|
|
||||||
const LastItemRadius = styled('div')({
|
const LastItemRadius = styled('div')({
|
||||||
@@ -212,9 +216,9 @@ const LastItemRadius = styled('div')({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '-0.5px',
|
left: '-0.5px',
|
||||||
top: 0,
|
top: 0,
|
||||||
height: ITEM_POINT_HEIGHT,
|
height: TREE_LINE_TOP_OFFSET,
|
||||||
bottom: '50%',
|
bottom: '50%',
|
||||||
width: '16px',
|
width: TREE_LINE_WIDTH,
|
||||||
borderWidth: '1px',
|
borderWidth: '1px',
|
||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
borderLeftColor: TREE_COLOR,
|
borderLeftColor: TREE_COLOR,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { BlockEditor } from './editor';
|
import type { BlockEditor } from './editor';
|
||||||
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
import { styled, usePatchNodes } from '@toeverything/components/ui';
|
||||||
import type { FC, PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||||
import { EditorProvider } from './Contexts';
|
import { EditorProvider } from './Contexts';
|
||||||
import { SelectionRect, SelectionRef } from './Selection';
|
import { SelectionRect, SelectionRef } from './Selection';
|
||||||
@@ -24,11 +24,11 @@ interface RenderRootProps {
|
|||||||
const MAX_PAGE_WIDTH = 5000;
|
const MAX_PAGE_WIDTH = 5000;
|
||||||
export const MIN_PAGE_WIDTH = 1480;
|
export const MIN_PAGE_WIDTH = 1480;
|
||||||
|
|
||||||
export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
export const RenderRoot = ({
|
||||||
editor,
|
editor,
|
||||||
editorElement,
|
editorElement,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}: PropsWithChildren<RenderRootProps>) => {
|
||||||
const selectionRef = useRef<SelectionRef>(null);
|
const selectionRef = useRef<SelectionRef>(null);
|
||||||
const triggeredBySelect = useRef(false);
|
const triggeredBySelect = useRef(false);
|
||||||
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
||||||
@@ -102,6 +102,12 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
editor.getHooks().onRootNodeMouseLeave(event);
|
editor.getHooks().onRootNodeMouseLeave(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onContextmenu = (
|
||||||
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||||
|
) => {
|
||||||
|
selectionRef.current?.onContextmenu(event);
|
||||||
|
};
|
||||||
|
|
||||||
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
|
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
|
||||||
// IMP move into keyboard managers?
|
// IMP move into keyboard managers?
|
||||||
editor.getHooks().onRootNodeKeyDown(event);
|
editor.getHooks().onRootNodeKeyDown(event);
|
||||||
@@ -146,6 +152,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = (event: React.DragEvent<Element>) => {
|
const onDrop = (event: React.DragEvent<Element>) => {
|
||||||
|
event.preventDefault();
|
||||||
editor.dragDropManager.handlerEditorDrop(event);
|
editor.dragDropManager.handlerEditorDrop(event);
|
||||||
editor.getHooks().onRootNodeDrop(event);
|
editor.getHooks().onRootNodeDrop(event);
|
||||||
};
|
};
|
||||||
@@ -165,6 +172,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
|||||||
onMouseUp={onMouseUp}
|
onMouseUp={onMouseUp}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
onMouseOut={onMouseOut}
|
onMouseOut={onMouseOut}
|
||||||
|
onContextMenu={onContextmenu}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
onKeyDownCapture={onKeyDownCapture}
|
onKeyDownCapture={onKeyDownCapture}
|
||||||
onKeyUp={onKeyUp}
|
onKeyUp={onKeyUp}
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ export type SelectionRef = {
|
|||||||
onMouseDown: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
onMouseDown: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onMouseMove: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
onMouseMove: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
onMouseUp: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
onMouseUp: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
|
onContextmenu: (
|
||||||
|
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||||
|
) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFixedPoint = (
|
const getFixedPoint = (
|
||||||
@@ -207,10 +210,17 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
scrollManager.stopAutoScroll();
|
scrollManager.stopAutoScroll();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onContextmenu = () => {
|
||||||
|
if (mouseType.current === 'down') {
|
||||||
|
onMouseUp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
onMouseMove,
|
onMouseMove,
|
||||||
onMouseUp,
|
onMouseUp,
|
||||||
|
onContextmenu,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -223,7 +233,8 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
|||||||
startPointRef.current &&
|
startPointRef.current &&
|
||||||
endPointRef.current &&
|
endPointRef.current &&
|
||||||
scrollManager.scrollContainer &&
|
scrollManager.scrollContainer &&
|
||||||
scrollContainerRect.current
|
scrollContainerRect.current &&
|
||||||
|
mouseType.current === 'down'
|
||||||
) {
|
) {
|
||||||
const xSign = DIRECTION_VALUE_MAP[direction[0]] || 0;
|
const xSign = DIRECTION_VALUE_MAP[direction[0]] || 0;
|
||||||
const ySign = DIRECTION_VALUE_MAP[direction[1]] || 0;
|
const ySign = DIRECTION_VALUE_MAP[direction[1]] || 0;
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import {
|
|||||||
Popover,
|
Popover,
|
||||||
type PopoverProps,
|
type PopoverProps,
|
||||||
PopperHandler,
|
PopperHandler,
|
||||||
|
Tag,
|
||||||
|
type PopperProps,
|
||||||
} from '@toeverything/components/ui';
|
} from '@toeverything/components/ui';
|
||||||
|
import { TagsIcon } from '@toeverything/components/icons';
|
||||||
|
|
||||||
import { CreatePendantPanel } from './pendant-operation-panel';
|
import { CreatePendantPanel } from './pendant-operation-panel';
|
||||||
import { IconButton } from './StyledComponent';
|
import { IconButton } from './StyledComponent';
|
||||||
import { AsyncBlock } from '../editor';
|
import { AsyncBlock } from '../editor';
|
||||||
@@ -13,17 +17,21 @@ type Props = {
|
|||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
onSure?: () => void;
|
onSure?: () => void;
|
||||||
iconStyle?: CSSProperties;
|
iconStyle?: CSSProperties;
|
||||||
|
useAddIcon?: boolean;
|
||||||
} & Omit<PopoverProps, 'content'>;
|
} & Omit<PopoverProps, 'content'>;
|
||||||
export const AddPendantPopover = ({
|
export const AddPendantPopover = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
iconStyle,
|
iconStyle,
|
||||||
|
useAddIcon = true,
|
||||||
...popoverProps
|
...popoverProps
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const popoverHandlerRef = useRef<PopperHandler>();
|
const popoverHandlerRef = useRef<PopperHandler>();
|
||||||
|
const popperRef = useRef<any>();
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
ref={popoverHandlerRef}
|
popperHandlerRef={popoverHandlerRef}
|
||||||
|
popperRef={popperRef}
|
||||||
content={
|
content={
|
||||||
<CreatePendantPanel
|
<CreatePendantPanel
|
||||||
block={block}
|
block={block}
|
||||||
@@ -31,6 +39,9 @@ export const AddPendantPopover = ({
|
|||||||
popoverHandlerRef.current?.setVisible(false);
|
popoverHandlerRef.current?.setVisible(false);
|
||||||
onSure?.();
|
onSure?.();
|
||||||
}}
|
}}
|
||||||
|
onTypeChange={() => {
|
||||||
|
popperRef.current?.update?.();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@@ -38,9 +49,25 @@ export const AddPendantPopover = ({
|
|||||||
style={{ padding: 0 }}
|
style={{ padding: 0 }}
|
||||||
{...popoverProps}
|
{...popoverProps}
|
||||||
>
|
>
|
||||||
<IconButton style={{ marginRight: 12, ...iconStyle }}>
|
{useAddIcon ? (
|
||||||
<Add sx={{ fontSize: 14 }} />
|
<IconButton style={{ marginRight: 12, ...iconStyle }}>
|
||||||
</IconButton>
|
<Add sx={{ fontSize: 14 }} />
|
||||||
|
</IconButton>
|
||||||
|
) : (
|
||||||
|
<Tag
|
||||||
|
style={{
|
||||||
|
background: '#F5F7F8',
|
||||||
|
color: '#98ACBD',
|
||||||
|
marginRight: 12,
|
||||||
|
marginBottom: 8,
|
||||||
|
}}
|
||||||
|
startElement={
|
||||||
|
<TagsIcon style={{ fontSize: 14, marginRight: 4 }} />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Tag App
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { styled } from '@toeverything/components/ui';
|
|||||||
import type { AsyncBlock } from '../editor';
|
import type { AsyncBlock } from '../editor';
|
||||||
import { PendantPopover } from './pendant-popover';
|
import { PendantPopover } from './pendant-popover';
|
||||||
import { PendantRender } from './pendant-render';
|
import { PendantRender } from './pendant-render';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { getRecastItemValue, useRecastBlockMeta } from '../recast-block';
|
||||||
/**
|
/**
|
||||||
* @deprecated
|
* @deprecated
|
||||||
*/
|
*/
|
||||||
@@ -10,17 +12,31 @@ interface BlockTagProps {
|
|||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
export const BlockPendantProvider = ({
|
||||||
block,
|
block,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}: PropsWithChildren<BlockTagProps>) => {
|
||||||
|
const triggerRef = useRef<HTMLDivElement>();
|
||||||
|
const { getProperties } = useRecastBlockMeta();
|
||||||
|
const properties = getProperties();
|
||||||
|
const { getValue } = getRecastItemValue(block);
|
||||||
|
const showTriggerLine =
|
||||||
|
properties.filter(property => getValue(property.id)).length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
{children}
|
{children}
|
||||||
|
|
||||||
<PendantPopover block={block}>
|
{showTriggerLine ? (
|
||||||
<StyledTriggerLine />
|
<StyledPendantContainer ref={triggerRef}>
|
||||||
</PendantPopover>
|
<PendantPopover
|
||||||
|
block={block}
|
||||||
|
container={triggerRef.current}
|
||||||
|
>
|
||||||
|
<StyledTriggerLine />
|
||||||
|
</PendantPopover>
|
||||||
|
</StyledPendantContainer>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<PendantRender block={block} />
|
<PendantRender block={block} />
|
||||||
</Container>
|
</Container>
|
||||||
@@ -28,7 +44,7 @@ export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const LINE_GAP = 16;
|
export const LINE_GAP = 16;
|
||||||
const TAG_GAP = 4;
|
export const TAG_GAP = 4;
|
||||||
|
|
||||||
const StyledTriggerLine = styled('div')({
|
const StyledTriggerLine = styled('div')({
|
||||||
padding: `${TAG_GAP}px 0`,
|
padding: `${TAG_GAP}px 0`,
|
||||||
@@ -43,10 +59,12 @@ const StyledTriggerLine = styled('div')({
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
height: '2px',
|
height: '2px',
|
||||||
background: '#dadada',
|
background: '#dadada',
|
||||||
display: 'none',
|
display: 'flex',
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '0',
|
left: '0',
|
||||||
top: '4px',
|
top: '4px',
|
||||||
|
transition: 'opacity .2s',
|
||||||
|
opacity: '0',
|
||||||
},
|
},
|
||||||
'::after': {
|
'::after': {
|
||||||
content: "''",
|
content: "''",
|
||||||
@@ -60,18 +78,24 @@ const StyledTriggerLine = styled('div')({
|
|||||||
transition: 'width .3s',
|
transition: 'width .3s',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const StyledPendantContainer = styled('div')({
|
||||||
const Container = styled('div')({
|
width: '100px',
|
||||||
position: 'relative',
|
|
||||||
paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`,
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
[StyledTriggerLine.toString()]: {
|
[`${StyledTriggerLine}`]: {
|
||||||
'&::before': {
|
|
||||||
display: 'flex',
|
|
||||||
},
|
|
||||||
'&::after': {
|
'&::after': {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const Container = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
|
padding: `${TAG_GAP * 2}px 0 ${LINE_GAP - TAG_GAP * 4}px 0`,
|
||||||
|
'&:hover': {
|
||||||
|
[`${StyledTriggerLine}`]: {
|
||||||
|
'&::before': {
|
||||||
|
opacity: '1',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import React, {
|
import React from 'react';
|
||||||
FC,
|
|
||||||
FunctionComponent,
|
|
||||||
PropsWithChildren,
|
|
||||||
CSSProperties,
|
|
||||||
} from 'react';
|
|
||||||
import { Tag, type TagProps } from '@toeverything/components/ui';
|
import { Tag, type TagProps } from '@toeverything/components/ui';
|
||||||
import {
|
import {
|
||||||
DateValue,
|
DateValue,
|
||||||
|
|||||||
@@ -98,8 +98,8 @@ export const pendantConfig: { [key: string]: PendantConfig } = {
|
|||||||
},
|
},
|
||||||
[PendantTypes.Status]: {
|
[PendantTypes.Status]: {
|
||||||
iconName: IconNames.STATUS,
|
iconName: IconNames.STATUS,
|
||||||
background: ['#C5FBE0', '#FFF5AB', '#FFCECE', '#E3DEFF'],
|
background: ['#FFCECE', '#FFF5AB', '#C5FBE0', '#E3DEFF'],
|
||||||
color: ['#05683D', '#896406', '#AF1212', '#511AAB'],
|
color: ['#AF1212', '#896406', '#05683D', '#511AAB'],
|
||||||
},
|
},
|
||||||
[PendantTypes.Select]: {
|
[PendantTypes.Select]: {
|
||||||
iconName: IconNames.SINGLE_SELECT,
|
iconName: IconNames.SINGLE_SELECT,
|
||||||
|
|||||||
+4
-2
@@ -29,6 +29,7 @@ export const PendantHistoryPanel = ({
|
|||||||
|
|
||||||
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
||||||
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
||||||
|
const historyPanelRef = useRef<HTMLDivElement>();
|
||||||
const { getValueHistory } = getRecastItemValue(block);
|
const { getValueHistory } = getRecastItemValue(block);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -84,13 +85,13 @@ export const PendantHistoryPanel = ({
|
|||||||
}, [block, getProperties, groupBlock, recastBlock]);
|
}, [block, getProperties, groupBlock, recastBlock]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledPendantHistoryPanel>
|
<StyledPendantHistoryPanel ref={historyPanelRef}>
|
||||||
{history.map(item => {
|
{history.map(item => {
|
||||||
const property = getProperty(item.id);
|
const property = getProperty(item.id);
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
key={item.id}
|
key={item.id}
|
||||||
ref={ref => {
|
popperHandlerRef={ref => {
|
||||||
popoverHandlerRef.current[item.id] = ref;
|
popoverHandlerRef.current[item.id] = ref;
|
||||||
}}
|
}}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
@@ -116,6 +117,7 @@ export const PendantHistoryPanel = ({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
trigger="click"
|
trigger="click"
|
||||||
|
container={historyPanelRef.current}
|
||||||
>
|
>
|
||||||
<PendantTag
|
<PendantTag
|
||||||
style={{
|
style={{
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export type ModifyPanelProps = {
|
|||||||
iconConfig?: PendantConfig;
|
iconConfig?: PendantConfig;
|
||||||
isStatusSelect?: boolean;
|
isStatusSelect?: boolean;
|
||||||
property?: RecastMetaProperty;
|
property?: RecastMetaProperty;
|
||||||
|
onTypeChange?: (type: PendantTypes) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ModifyPanelContentProps = {
|
export type ModifyPanelContentProps = {
|
||||||
|
|||||||
+21
-12
@@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
import { message, Option, Select } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
|
||||||
import { AsyncBlock } from '../../editor';
|
import { AsyncBlock } from '../../editor';
|
||||||
|
|
||||||
import { IconMap, pendantOptions } from '../config';
|
import { IconMap, pendantOptions } from '../config';
|
||||||
@@ -8,7 +7,6 @@ import { PendantOptions } from '../types';
|
|||||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||||
import {
|
import {
|
||||||
StyledDivider,
|
StyledDivider,
|
||||||
StyledInputEndAdornment,
|
|
||||||
StyledOperationLabel,
|
StyledOperationLabel,
|
||||||
StyledOperationTitle,
|
StyledOperationTitle,
|
||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
@@ -18,15 +16,19 @@ import {
|
|||||||
generateRandomFieldName,
|
generateRandomFieldName,
|
||||||
generateInitialOptions,
|
generateInitialOptions,
|
||||||
getPendantConfigByType,
|
getPendantConfigByType,
|
||||||
|
checkPendantForm,
|
||||||
} from '../utils';
|
} from '../utils';
|
||||||
|
import { FieldTitleInput } from './FieldTitleInput';
|
||||||
import { useOnCreateSure } from './hooks';
|
import { useOnCreateSure } from './hooks';
|
||||||
|
|
||||||
export const CreatePendantPanel = ({
|
export const CreatePendantPanel = ({
|
||||||
block,
|
block,
|
||||||
onSure,
|
onSure,
|
||||||
|
onTypeChange,
|
||||||
}: {
|
}: {
|
||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
onSure?: () => void;
|
onSure?: () => void;
|
||||||
|
onTypeChange?: (option: PendantOptions) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [selectedOption, setSelectedOption] = useState<PendantOptions>();
|
const [selectedOption, setSelectedOption] = useState<PendantOptions>();
|
||||||
const [fieldName, setFieldName] = useState<string>('');
|
const [fieldName, setFieldName] = useState<string>('');
|
||||||
@@ -37,6 +39,10 @@ export const CreatePendantPanel = ({
|
|||||||
setFieldName(generateRandomFieldName(selectedOption.type));
|
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||||
}, [selectedOption]);
|
}, [selectedOption]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onTypeChange?.(selectedOption);
|
||||||
|
}, [selectedOption, onTypeChange]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledPopoverWrapper>
|
<StyledPopoverWrapper>
|
||||||
<StyledOperationTitle>Add Field</StyledOperationTitle>
|
<StyledOperationTitle>Add Field</StyledOperationTitle>
|
||||||
@@ -67,19 +73,11 @@ export const CreatePendantPanel = ({
|
|||||||
})}
|
})}
|
||||||
</Select>
|
</Select>
|
||||||
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
||||||
<Input
|
<FieldTitleInput
|
||||||
value={fieldName}
|
value={fieldName}
|
||||||
placeholder="Input your field name here"
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setFieldName(e.target.value);
|
setFieldName(e.target.value);
|
||||||
}}
|
}}
|
||||||
endAdornment={
|
|
||||||
<Tooltip content="Help info here">
|
|
||||||
<StyledInputEndAdornment>
|
|
||||||
<HelpCenterIcon />
|
|
||||||
</StyledInputEndAdornment>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
{selectedOption ? (
|
{selectedOption ? (
|
||||||
<>
|
<>
|
||||||
@@ -98,6 +96,17 @@ export const CreatePendantPanel = ({
|
|||||||
)}
|
)}
|
||||||
iconConfig={getPendantConfigByType(selectedOption.type)}
|
iconConfig={getPendantConfigByType(selectedOption.type)}
|
||||||
onSure={async (type, newPropertyItem, newValue) => {
|
onSure={async (type, newPropertyItem, newValue) => {
|
||||||
|
const checkResult = checkPendantForm(
|
||||||
|
type,
|
||||||
|
fieldName,
|
||||||
|
newPropertyItem,
|
||||||
|
newValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!checkResult.passed) {
|
||||||
|
await message.error(checkResult.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await onCreateSure({
|
await onCreateSure({
|
||||||
type,
|
type,
|
||||||
newPropertyItem,
|
newPropertyItem,
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Input, Tooltip, InputProps } from '@toeverything/components/ui';
|
||||||
|
import { StyledInputEndAdornment } from '../StyledComponent';
|
||||||
|
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||||
|
|
||||||
|
export const FieldTitleInput = (props: InputProps) => {
|
||||||
|
return (
|
||||||
|
<Input
|
||||||
|
placeholder="Input your field name here"
|
||||||
|
endAdornment={
|
||||||
|
<Tooltip
|
||||||
|
offset={[15, -5]}
|
||||||
|
content="Name your tag of the current series"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<StyledInputEndAdornment>
|
||||||
|
<HelpCenterIcon />
|
||||||
|
</StyledInputEndAdornment>
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
+15
-14
@@ -1,6 +1,5 @@
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Input, Tooltip } from '@toeverything/components/ui';
|
import { message } from '@toeverything/components/ui';
|
||||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
|
||||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||||
import type { AsyncBlock } from '../../editor';
|
import type { AsyncBlock } from '../../editor';
|
||||||
import {
|
import {
|
||||||
@@ -8,17 +7,16 @@ import {
|
|||||||
type RecastBlockValue,
|
type RecastBlockValue,
|
||||||
type RecastMetaProperty,
|
type RecastMetaProperty,
|
||||||
} from '../../recast-block';
|
} from '../../recast-block';
|
||||||
import { getPendantConfigByType } from '../utils';
|
import { checkPendantForm, getPendantConfigByType } from '../utils';
|
||||||
import {
|
import {
|
||||||
StyledPopoverWrapper,
|
StyledPopoverWrapper,
|
||||||
StyledOperationLabel,
|
StyledOperationLabel,
|
||||||
StyledInputEndAdornment,
|
|
||||||
StyledDivider,
|
StyledDivider,
|
||||||
StyledPopoverContent,
|
StyledPopoverContent,
|
||||||
StyledPopoverSubTitle,
|
StyledPopoverSubTitle,
|
||||||
} from '../StyledComponent';
|
} from '../StyledComponent';
|
||||||
import { IconMap, pendantOptions } from '../config';
|
import { IconMap, pendantOptions } from '../config';
|
||||||
|
import { FieldTitleInput } from './FieldTitleInput';
|
||||||
import { useOnUpdateSure } from './hooks';
|
import { useOnUpdateSure } from './hooks';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -63,19 +61,11 @@ export const UpdatePendantPanel = ({
|
|||||||
</StyledPopoverContent>
|
</StyledPopoverContent>
|
||||||
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
||||||
{titleEditable ? (
|
{titleEditable ? (
|
||||||
<Input
|
<FieldTitleInput
|
||||||
value={fieldName}
|
value={fieldName}
|
||||||
placeholder="Input your field name here"
|
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setFieldName(e.target.value);
|
setFieldName(e.target.value);
|
||||||
}}
|
}}
|
||||||
endAdornment={
|
|
||||||
<Tooltip content="Help info here">
|
|
||||||
<StyledInputEndAdornment>
|
|
||||||
<HelpCenterIcon />
|
|
||||||
</StyledInputEndAdornment>
|
|
||||||
</Tooltip>
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledPopoverContent>{property.name}</StyledPopoverContent>
|
<StyledPopoverContent>{property.name}</StyledPopoverContent>
|
||||||
@@ -98,6 +88,17 @@ export const UpdatePendantPanel = ({
|
|||||||
property={property}
|
property={property}
|
||||||
type={property.type}
|
type={property.type}
|
||||||
onSure={async (type, newPropertyItem, newValue) => {
|
onSure={async (type, newPropertyItem, newValue) => {
|
||||||
|
const checkResult = checkPendantForm(
|
||||||
|
type,
|
||||||
|
fieldName,
|
||||||
|
newPropertyItem,
|
||||||
|
newValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!checkResult.passed) {
|
||||||
|
await message.error(checkResult.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
await onUpdateSure({
|
await onUpdateSure({
|
||||||
type,
|
type,
|
||||||
newPropertyItem,
|
newPropertyItem,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user