mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-12 23:56:36 +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",
|
||||
"repoType": "github",
|
||||
"repoHost": "https://github.com",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"imageSize": 100,
|
||||
"imageSize": 50,
|
||||
"commit": false,
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
@@ -115,11 +115,120 @@
|
||||
"login": "uptonking",
|
||||
"name": "Jin Yao",
|
||||
"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": [
|
||||
"code",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "HeJiachen-PM",
|
||||
"name": "HeJiachen-PM",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/79301703?v=4",
|
||||
"profile": "https://github.com/HeJiachen-PM",
|
||||
"contributions": [
|
||||
"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",
|
||||
"prettier.prettierPath": "./node_modules/prettier",
|
||||
"cSpell.words": [
|
||||
"AUTOINCREMENT",
|
||||
"Backlinks",
|
||||
"blockdb",
|
||||
"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]: 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 -->
|
||||
|
||||
[](https://affine.pro)
|
||||
[](https://github.com/toeverything/AFFiNE)
|
||||
[![All Contributors][all-contributors-badge]](#contributors)
|
||||
[](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
|
||||
|
||||
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.
|
||||
And, thanks to Lee who [made a desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out.
|
||||
<h3 align="center">
|
||||
🥳🥳🥳 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.
|
||||
|
||||
# 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
|
||||
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
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 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
|
||||
|
||||
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
|
||||
|
||||
@@ -107,28 +125,28 @@ Get our latest [release notes](https://github.com/toeverything/AFFiNE/wiki) from
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
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:
|
||||
|
||||
- Transformable
|
||||
- Every block can be transformed equally well as a database
|
||||
- e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view.
|
||||
- Every doc can be turned into a whiteboard
|
||||
- Every block can be transformed equally
|
||||
- e.g. you can create a todo in Markdown in the text view and then later edit it in the kanban view.
|
||||
- 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.
|
||||
- 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.
|
||||
- Pages and blocks are searched and organized based on connected graphs, not tree-like paths.
|
||||
- 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
|
||||
- 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.
|
||||
With AFFiNE, every block group has infinite views, for you to keep your single source of truth.
|
||||
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.
|
||||
|
||||
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 -->
|
||||
<table>
|
||||
<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="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="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/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://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://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/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://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=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=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=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=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=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=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>
|
||||
<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/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://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://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/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=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=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"><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>
|
||||
</table>
|
||||
|
||||
|
||||
@@ -4,11 +4,11 @@ describe('ligo-virgo', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('basic load check', () => {
|
||||
getTitle().contains('Get Started with AFFiNE');
|
||||
getTitle().contains('👋 Get Started with AFFINE');
|
||||
|
||||
cy.get('.block_container').contains('The Essentials');
|
||||
|
||||
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 />;
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
if (!user || !pathname.startsWith(`/${user.id}`)) {
|
||||
return (
|
||||
<Navigate
|
||||
to={unauthorizedRedirectTo}
|
||||
|
||||
@@ -3,7 +3,7 @@ import * as uiIcons from '@toeverything/components/icons';
|
||||
import { message, styled } from '@toeverything/components/ui';
|
||||
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 = () => {
|
||||
copy(`<${name} />`);
|
||||
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');
|
||||
|
||||
export const Icons: FC = () => {
|
||||
export const Icons = () => {
|
||||
const ref = useRef<HTMLHeadingElement>(null);
|
||||
return (
|
||||
<Container>
|
||||
|
||||
@@ -22,8 +22,8 @@ export function WorkspaceHome() {
|
||||
workspace_id,
|
||||
user_initial_page_id,
|
||||
TemplateFactory.generatePageTemplateByGroupKeys({
|
||||
name: null,
|
||||
groupKeys: ['todolist'],
|
||||
name: '👋 Get Started with AFFINE',
|
||||
groupKeys: ['getStartedGroup0', 'getStartedGroup1'],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { useEffect, useRef, type UIEvent, useState } from 'react';
|
||||
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 {
|
||||
CalendarHeatmap,
|
||||
@@ -15,10 +11,13 @@ import {
|
||||
import { CollapsibleTitle } from '@toeverything/components/common';
|
||||
import {
|
||||
useShowSpaceSidebar,
|
||||
useUserAndSpaces,
|
||||
usePageClientWidth,
|
||||
} 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 { 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 {
|
||||
AddIcon,
|
||||
ArrowDropDownIcon,
|
||||
ArrowRightIcon,
|
||||
} from '@toeverything/components/icons';
|
||||
import { services } from '@toeverything/datasource/db-service';
|
||||
import {
|
||||
usePageTree,
|
||||
useCalendarHeatmap,
|
||||
usePageTree,
|
||||
} 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',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
const StyledBtn = styled('div')({
|
||||
color: '#98ACBD',
|
||||
textTransform: 'none',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
cursor: 'pointer',
|
||||
userSelect: 'none',
|
||||
flex: 1,
|
||||
marginLeft: '12px',
|
||||
});
|
||||
|
||||
export type CollapsiblePageTreeProps = {
|
||||
@@ -72,7 +72,7 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingRight: 1,
|
||||
paddingRight: '12px',
|
||||
'&:hover': {
|
||||
background: '#f5f7f8',
|
||||
borderRadius: '5px',
|
||||
@@ -81,27 +81,18 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
|
||||
onMouseEnter={() => setNewPageBtnVisible(true)}
|
||||
onMouseLeave={() => setNewPageBtnVisible(false)}
|
||||
>
|
||||
<StyledContainer>
|
||||
{open ? (
|
||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||
) : (
|
||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||
)}
|
||||
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
||||
{title}
|
||||
</StyledBtn>
|
||||
</StyledContainer>
|
||||
<StyledBtn onClick={() => setOpen(prev => !prev)}>
|
||||
{title}
|
||||
</StyledBtn>
|
||||
|
||||
{newPageBtnVisible && (
|
||||
<AddIcon
|
||||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
color: '#98ACBD',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
<IconButton
|
||||
size="small"
|
||||
hoverColor="#E0E6EB"
|
||||
onClick={create_page}
|
||||
/>
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
{children ? (
|
||||
|
||||
@@ -10,13 +10,13 @@ const StyledTabs = styled('div')(({ theme }) => {
|
||||
display: 'flex',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: theme.affine.palette.primary,
|
||||
};
|
||||
});
|
||||
|
||||
const StyledTabTitle = styled('div', {
|
||||
shouldForwardProp: (prop: string) => !['isActive'].includes(prop),
|
||||
})<{ isActive?: boolean; isDisabled?: boolean }>`
|
||||
const StyledTabTitle = styled('div')<{
|
||||
isActive?: boolean;
|
||||
isDisabled?: boolean;
|
||||
}>`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -26,13 +26,18 @@ const StyledTabTitle = styled('div', {
|
||||
padding-top: 4px;
|
||||
border-top: 2px solid #ecf1fb;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
color: ${({ theme, isActive }) =>
|
||||
isActive ? theme.affine.palette.primary : 'rgba(62, 111, 219, 0.6)'};
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background-color: ${({ isActive, theme }) =>
|
||||
isActive ? theme.affine.palette.primary : ''};
|
||||
isActive
|
||||
? theme.affine.palette.primary
|
||||
: 'rgba(62, 111, 219, 0.6)'};
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: -2px;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import {
|
||||
styled,
|
||||
MuiOutlinedInput as OutlinedInput,
|
||||
} from '@toeverything/components/ui';
|
||||
import { styled, Input } from '@toeverything/components/ui';
|
||||
import { PinIcon } from '@toeverything/components/icons';
|
||||
import {
|
||||
useUserAndSpaces,
|
||||
useShowSpaceSidebar,
|
||||
} 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 { Logo } from './components/logo/Logo';
|
||||
|
||||
@@ -89,18 +92,16 @@ export const WorkspaceName = () => {
|
||||
const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar();
|
||||
const [inRename, setInRename] = useState(false);
|
||||
const [workspaceName, setWorkspaceName] = useState('');
|
||||
const [workspaceId, setWorkspaceId] = useState('');
|
||||
|
||||
const fetchWorkspaceName = useCallback(async () => {
|
||||
if (!currentSpaceId) {
|
||||
return;
|
||||
}
|
||||
const [name, workspaceId] = await Promise.all([
|
||||
services.api.userConfig.getWorkspaceName(currentSpaceId),
|
||||
services.api.userConfig.getWorkspaceId(currentSpaceId),
|
||||
]);
|
||||
const name = await services.api.userConfig.getWorkspaceName(
|
||||
currentSpaceId
|
||||
);
|
||||
|
||||
setWorkspaceName(name);
|
||||
setWorkspaceId(workspaceId);
|
||||
}, [currentSpaceId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -124,24 +125,24 @@ export const WorkspaceName = () => {
|
||||
};
|
||||
}, [currentSpaceId, fetchWorkspaceName]);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setInRename(false);
|
||||
}
|
||||
},
|
||||
[]
|
||||
);
|
||||
const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
setInRename(false);
|
||||
}
|
||||
}, []);
|
||||
const handleChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
services.api.userConfig.setWorkspaceName(
|
||||
async (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const name = e.target.value;
|
||||
|
||||
await setWorkspaceName(name);
|
||||
await services.api.userConfig.setWorkspaceName(
|
||||
currentSpaceId,
|
||||
e.currentTarget.value
|
||||
name
|
||||
);
|
||||
},
|
||||
[]
|
||||
[currentSpaceId]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -165,7 +166,8 @@ export const WorkspaceName = () => {
|
||||
<StyledWorkspace>
|
||||
{inRename ? (
|
||||
<WorkspaceReNameContainer>
|
||||
<OutlinedInput
|
||||
<Input
|
||||
autoFocus
|
||||
style={{ width: '140px', height: '28px' }}
|
||||
value={workspaceName}
|
||||
onChange={handleChange}
|
||||
@@ -176,7 +178,7 @@ export const WorkspaceName = () => {
|
||||
) : (
|
||||
<WorkspaceNameContainer>
|
||||
<span onClick={() => setInRename(true)}>
|
||||
{workspaceName || workspaceId}
|
||||
{workspaceName || currentSpaceId}
|
||||
</span>
|
||||
</WorkspaceNameContainer>
|
||||
)}
|
||||
|
||||
@@ -91,9 +91,9 @@ module.exports = function (webpackConfig) {
|
||||
priority: -9,
|
||||
chunks: 'all',
|
||||
},
|
||||
vender: {
|
||||
vendor: {
|
||||
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
||||
name: 'vender',
|
||||
name: 'vendor',
|
||||
priority: -10,
|
||||
chunks: 'all',
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy';
|
||||
import GitHubIcon from '@mui/icons-material/GitHub';
|
||||
import RedditIcon from '@mui/icons-material/Reddit';
|
||||
import TelegramIcon from '@mui/icons-material/Telegram';
|
||||
import { LogoIcon } from '@toeverything/components/icons';
|
||||
// eslint-disable-next-line no-restricted-imports
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
|
||||
@@ -27,7 +28,7 @@ const DiscordIcon = (props: any) => {
|
||||
viewBox="0 0 71 55"
|
||||
fill="currentcolor"
|
||||
>
|
||||
<g clip-path="url(#clip0)">
|
||||
<g clipPath="url(#clip0)">
|
||||
<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"
|
||||
fill="currentcolor"
|
||||
@@ -203,9 +204,7 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => {
|
||||
return (
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.location.replace(
|
||||
'https://github.com/toeverything/AFFiNE'
|
||||
);
|
||||
window.open('https://github.com/toeverything/AFFiNE');
|
||||
}}
|
||||
{...(props.flat ? { variant: 'plain' } : {})}
|
||||
{...{
|
||||
@@ -236,7 +235,50 @@ const GitHub = (props: { center?: boolean; flat?: boolean }) => {
|
||||
startIcon={<GitHubIcon />}
|
||||
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>
|
||||
);
|
||||
};
|
||||
@@ -258,7 +300,10 @@ export function App() {
|
||||
<Grid
|
||||
container
|
||||
spacing={2}
|
||||
sx={{ maxWidth: '1280px', margin: 'auto' }}
|
||||
sx={{
|
||||
maxWidth: '1280px',
|
||||
margin: 'auto',
|
||||
}}
|
||||
>
|
||||
<Grid xs={6}>
|
||||
<Button
|
||||
@@ -283,9 +328,7 @@ export function App() {
|
||||
<GitHub flat />
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.location.replace(
|
||||
'https://blog.affine.pro'
|
||||
);
|
||||
window.open('https://blog.affine.pro');
|
||||
}}
|
||||
variant="plain"
|
||||
sx={{
|
||||
@@ -407,10 +450,8 @@ export function App() {
|
||||
marginBottom: '12vh!important',
|
||||
}}
|
||||
>
|
||||
<Typography level="h2" sx={{ alignSelf: 'center' }}>
|
||||
Try it on
|
||||
</Typography>
|
||||
<GitHub center />
|
||||
<AFFiNEOnline center />
|
||||
</Box>
|
||||
</Grid>
|
||||
<Grid
|
||||
@@ -447,7 +488,7 @@ export function App() {
|
||||
level={matches ? 'h2' : 'h1'}
|
||||
fontWeight={'bold'}
|
||||
>
|
||||
It’s not just add-up of Docs, whiteboard, and
|
||||
It’s not just a collection of Docs, whiteboard, and
|
||||
tables.
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -465,11 +506,11 @@ export function App() {
|
||||
}}
|
||||
>
|
||||
<Typography fontSize="1.2em">
|
||||
Transform any building blocks as you like.
|
||||
Transform any building block as you like.
|
||||
</Typography>
|
||||
<Typography fontSize="1.2em">
|
||||
Say goodbye to redundancy, and keep all your
|
||||
knowledge minimal, in your way.
|
||||
Say goodbye to redundancy. Store your data once, and
|
||||
keep your data as you like it.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
@@ -512,15 +553,17 @@ export function App() {
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
Docs, kanban, and databases are all fully
|
||||
functional at any place. You always keep
|
||||
what-you-see-is-what-you-get.
|
||||
Docs, Kanbans, and Databases are all fully
|
||||
functional anywhere, anytime. A truly
|
||||
what-you-see-is-what-you-get environment for
|
||||
your data.
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize="1.2em"
|
||||
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>
|
||||
</Box>
|
||||
</Grid>
|
||||
@@ -588,21 +631,21 @@ export function App() {
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
No more chaos between so many views.
|
||||
No more chaos managing multiple views.
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
Set a TODO with Markdown, and manage it in
|
||||
Kanban.
|
||||
Set a TODO with Markdown, and seamlessly edit it
|
||||
within a Kanban.
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
Managing multi-dimensional tables should be
|
||||
simple as that.
|
||||
Managing multi-dimensional tables should be this
|
||||
simple – and now it is.
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
@@ -661,16 +704,20 @@ export function App() {
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
We don’t like cloud lock-in. Your data is always
|
||||
locally stored and secured, while you can still
|
||||
collaborate in real-time with others.
|
||||
We don’t like being locked-in, and neither should
|
||||
you. Privacy is at the foundation of everything we
|
||||
do, but it should not limit us that’s+ why there are
|
||||
no compromises.
|
||||
</Typography>
|
||||
<Typography
|
||||
fontSize="1.2em"
|
||||
style={{ marginBottom: '0.25em' }}
|
||||
>
|
||||
Privacy is the foundation of everything we do. But
|
||||
it is no excuse for the bad experience.
|
||||
Your data is yours; it is always locally stored and
|
||||
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>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
@@ -81,9 +81,9 @@ module.exports = function (webpackConfig) {
|
||||
priority: -9,
|
||||
chunks: 'all',
|
||||
},
|
||||
vender: {
|
||||
vendor: {
|
||||
test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/,
|
||||
name: 'vender',
|
||||
name: 'vendor',
|
||||
priority: -10,
|
||||
chunks: 'all',
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable filename-rules/match */
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { initializeApp } from 'firebase/app';
|
||||
import {
|
||||
GoogleAuthProvider,
|
||||
@@ -8,15 +8,7 @@ import {
|
||||
browserLocalPersistence,
|
||||
} from 'firebase/auth';
|
||||
|
||||
import { LogoImg } from '@toeverything/components/common';
|
||||
import {
|
||||
MuiButton,
|
||||
MuiBox,
|
||||
MuiGrid,
|
||||
MuiSnackbar,
|
||||
} from '@toeverything/components/ui';
|
||||
|
||||
import { Error } from './../error';
|
||||
import { MuiButton } from '@toeverything/components/ui';
|
||||
|
||||
const _firebaseConfig = {
|
||||
apiKey: 'AIzaSyD7A_VyGaKTXsPqtga9IbwrEsbWWc4rH3Y',
|
||||
@@ -75,7 +67,7 @@ const GoogleIcon = () => (
|
||||
</svg>
|
||||
);
|
||||
|
||||
export const Firebase = () => {
|
||||
export const Firebase = (props: { onError: () => void }) => {
|
||||
const [auth, provider] = useMemo(() => {
|
||||
const auth = getAuth(_app);
|
||||
auth.setPersistence(browserLocalPersistence);
|
||||
@@ -83,8 +75,6 @@ export const Firebase = () => {
|
||||
return [auth, provider];
|
||||
}, []);
|
||||
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const handleAuth = useCallback(() => {
|
||||
signInWithPopup(auth, provider).catch(error => {
|
||||
const errorCode = error.code;
|
||||
@@ -92,53 +82,19 @@ export const Firebase = () => {
|
||||
const email = error.customData.email;
|
||||
const credential = GoogleAuthProvider.credentialFromError(error);
|
||||
console.log(errorCode, errorMessage, email, credential);
|
||||
setError(true);
|
||||
setTimeout(() => setError(false), 3000);
|
||||
props.onError();
|
||||
});
|
||||
}, [auth, provider]);
|
||||
}, [auth, props, provider]);
|
||||
|
||||
return (
|
||||
<MuiGrid container>
|
||||
<MuiSnackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
open={error}
|
||||
message="Login 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
|
||||
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>
|
||||
<MuiButton
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
style={{ textTransform: 'none' }}
|
||||
startIcon={<GoogleIcon />}
|
||||
onClick={handleAuth}
|
||||
>
|
||||
Continue with Google
|
||||
</MuiButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 { Firebase } from './firebase';
|
||||
import { FileSystem } from './fs';
|
||||
import { Error } from './../error';
|
||||
|
||||
export function Login() {
|
||||
const [error, setError] = useState(false);
|
||||
|
||||
const onError = useCallback(() => {
|
||||
setError(true);
|
||||
setTimeout(() => setError(false), 3000);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <Authing /> */}
|
||||
<Firebase />
|
||||
</>
|
||||
<MuiGrid container>
|
||||
<MuiSnackbar
|
||||
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 */
|
||||
import * as React from 'react';
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from 'react';
|
||||
import { Renderer } from '@tldraw/core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import {
|
||||
@@ -132,13 +140,13 @@ export function Tldraw({
|
||||
getSession,
|
||||
tools,
|
||||
}: TldrawProps) {
|
||||
const [sId, set_sid] = React.useState(id);
|
||||
const [sId, setSid] = useState(id);
|
||||
const { pageClientWidth } = usePageClientWidth();
|
||||
// page padding left and right total 300px
|
||||
const editorShapeInitSize = pageClientWidth - 300;
|
||||
|
||||
// Create a new app when the component mounts.
|
||||
const [app, setApp] = React.useState(() => {
|
||||
const [app, setApp] = useState(() => {
|
||||
const app = new TldrawApp({
|
||||
id,
|
||||
callbacks,
|
||||
@@ -151,7 +159,7 @@ export function Tldraw({
|
||||
});
|
||||
|
||||
// Create a new app if the `id` prop changes.
|
||||
React.useLayoutEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (id === sId) return;
|
||||
const newApp = new TldrawApp({
|
||||
id,
|
||||
@@ -161,14 +169,14 @@ export function Tldraw({
|
||||
tools,
|
||||
});
|
||||
|
||||
set_sid(id);
|
||||
setSid(id);
|
||||
|
||||
setApp(newApp);
|
||||
}, [sId, id]);
|
||||
|
||||
// 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.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!document) return;
|
||||
|
||||
if (document.id === app.document.id) {
|
||||
@@ -179,34 +187,34 @@ export function Tldraw({
|
||||
}, [document, app]);
|
||||
|
||||
// Disable assets when the `disableAssets` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.setDisableAssets(disableAssets);
|
||||
}, [app, disableAssets]);
|
||||
|
||||
// Change the page when the `currentPageId` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!currentPageId) return;
|
||||
app.changePage(currentPageId);
|
||||
}, [currentPageId, app]);
|
||||
|
||||
// Toggle the app's readOnly mode when the `readOnly` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.readOnly = readOnly;
|
||||
}, [app, readOnly]);
|
||||
|
||||
// Toggle the app's darkMode when the `darkMode` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (darkMode !== app.settings.isDarkMode) {
|
||||
app.toggleDarkMode();
|
||||
}
|
||||
}, [app, darkMode]);
|
||||
|
||||
// Update the app's callbacks when any callback changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.callbacks = callbacks || {};
|
||||
}, [app, callbacks]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!window.document?.fonts) return;
|
||||
|
||||
@@ -260,7 +268,7 @@ interface InnerTldrawProps {
|
||||
showSponsorLink?: boolean;
|
||||
}
|
||||
|
||||
const InnerTldraw = React.memo(function InnerTldraw({
|
||||
const InnerTldraw = memo(function InnerTldraw({
|
||||
id,
|
||||
autofocus,
|
||||
showPages,
|
||||
@@ -276,7 +284,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
}: InnerTldrawProps) {
|
||||
const app = useTldrawApp();
|
||||
|
||||
const rWrapper = React.useRef<HTMLDivElement>(null);
|
||||
const rWrapper = useRef<HTMLDivElement>(null);
|
||||
|
||||
const state = app.useStore();
|
||||
|
||||
@@ -299,7 +307,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideResizeHandles;
|
||||
|
||||
// Custom rendering meta, with dark mode for shapes
|
||||
const meta: TDMeta = React.useMemo(() => {
|
||||
const meta: TDMeta = useMemo(() => {
|
||||
return { isDarkMode: settings.isDarkMode, app };
|
||||
}, [settings.isDarkMode, app]);
|
||||
|
||||
@@ -308,7 +316,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
: appState.selectByContain;
|
||||
|
||||
// Custom theme, based on darkmode
|
||||
const theme = React.useMemo(() => {
|
||||
const theme = useMemo(() => {
|
||||
const { selectByContain } = appState;
|
||||
const { isDarkMode, isCadSelectMode } = settings;
|
||||
|
||||
@@ -373,9 +381,11 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
!isSelecting ||
|
||||
!settings.showCloneHandles ||
|
||||
pageState.camera.zoom < 0.2;
|
||||
|
||||
return (
|
||||
<StyledLayout
|
||||
ref={rWrapper}
|
||||
panning={settings.forcePanning}
|
||||
tabIndex={-0}
|
||||
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,
|
||||
autofocus,
|
||||
}: {
|
||||
autofocus?: boolean;
|
||||
focusableRef: React.RefObject<HTMLDivElement>;
|
||||
focusableRef: RefObject<HTMLDivElement>;
|
||||
}) {
|
||||
useKeyboardShortcuts(focusableRef);
|
||||
useStylesheet();
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (autofocus) {
|
||||
focusableRef.current?.focus();
|
||||
}
|
||||
@@ -496,8 +506,8 @@ const OneOff = React.memo(function OneOff({
|
||||
return null;
|
||||
});
|
||||
|
||||
const StyledLayout = styled('div')<{ penColor: string }>(
|
||||
({ theme, penColor }) => {
|
||||
const StyledLayout = styled('div')<{ penColor: string; panning: boolean }>(
|
||||
({ theme, panning, penColor }) => {
|
||||
return {
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
@@ -509,6 +519,7 @@ const StyledLayout = styled('div')<{ penColor: string }>(
|
||||
overflow: 'hidden',
|
||||
boxSizing: 'border-box',
|
||||
outline: 'none',
|
||||
cursor: panning ? 'grab' : 'unset',
|
||||
|
||||
'& .tl-container': {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -34,10 +34,7 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
||||
return max[0];
|
||||
};
|
||||
|
||||
export const BorderColorConfig: FC<BorderColorConfigProps> = ({
|
||||
app,
|
||||
shapes,
|
||||
}) => {
|
||||
export const BorderColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
|
||||
const setBorderColor = (color: string) => {
|
||||
app.style({ stroke: color }, getShapeIds(shapes));
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { DeleteShapes } from './DeleteOperation';
|
||||
import { Lock, Unlock } from './LockOperation';
|
||||
import { FrameFillColorConfig } from './FrameFillColorConfig';
|
||||
|
||||
export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
export const CommandPanel = ({ app }: { app: TldrawApp }) => {
|
||||
const state = app.useStore();
|
||||
const bounds = TLDR.get_selected_bounds(state);
|
||||
const camera = app.useStore(
|
||||
@@ -23,7 +23,7 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
? app.getScreenPoint([bounds.minX, bounds.minY])
|
||||
: undefined;
|
||||
|
||||
const anchor = getAnchor({
|
||||
const anchorEl = getAnchor({
|
||||
x: point?.[0] || 0,
|
||||
y: (point?.[1] || 0) + 40,
|
||||
width: bounds?.width ? bounds.width * camera.zoom : 0,
|
||||
@@ -101,7 +101,7 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
<Popover
|
||||
trigger="click"
|
||||
visible={!!point}
|
||||
anchor={anchor}
|
||||
anchorEl={anchorEl}
|
||||
popoverDirection="none"
|
||||
content={
|
||||
<PopoverContainer>
|
||||
|
||||
@@ -10,7 +10,7 @@ interface DeleteShapesProps {
|
||||
shapes: TDShape[];
|
||||
}
|
||||
|
||||
export const DeleteShapes: FC<DeleteShapesProps> = ({ app, shapes }) => {
|
||||
export const DeleteShapes = ({ app, shapes }: DeleteShapesProps) => {
|
||||
const deleteShapes = () => {
|
||||
app.delete(getShapeIds(shapes));
|
||||
};
|
||||
|
||||
@@ -41,10 +41,7 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
||||
return max[0];
|
||||
};
|
||||
|
||||
export const FillColorConfig: FC<BorderColorConfigProps> = ({
|
||||
app,
|
||||
shapes,
|
||||
}) => {
|
||||
export const FillColorConfig = ({ app, shapes }: BorderColorConfigProps) => {
|
||||
const theme = useTheme();
|
||||
const setFillColor = (color: ColorType) => {
|
||||
app.style(
|
||||
|
||||
@@ -51,7 +51,7 @@ const _getFontSize = (shapes: TDShape[]): 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) => {
|
||||
app.style({ fontSize: size }, getShapeIds(shapes));
|
||||
};
|
||||
|
||||
@@ -40,10 +40,10 @@ const _getIconRenderColor = (shapes: TDShape[]) => {
|
||||
return max[0];
|
||||
};
|
||||
|
||||
export const FrameFillColorConfig: FC<BorderColorConfigProps> = ({
|
||||
export const FrameFillColorConfig = ({
|
||||
app,
|
||||
shapes,
|
||||
}) => {
|
||||
}: BorderColorConfigProps) => {
|
||||
const theme = useTheme();
|
||||
const setFillColor = (color: ColorType) => {
|
||||
app.style(
|
||||
|
||||
@@ -10,7 +10,7 @@ interface GroupAndUnGroupProps {
|
||||
shapes: TDShape[];
|
||||
}
|
||||
|
||||
export const Group: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
||||
export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||
const group = () => {
|
||||
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 = () => {
|
||||
app.ungroup(getShapeIds(shapes));
|
||||
};
|
||||
|
||||
@@ -10,7 +10,7 @@ interface GroupAndUnGroupProps {
|
||||
shapes: TDShape[];
|
||||
}
|
||||
|
||||
export const Lock: FC<GroupAndUnGroupProps> = ({ app, shapes }) => {
|
||||
export const Lock = ({ app, shapes }: GroupAndUnGroupProps) => {
|
||||
const lock = () => {
|
||||
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 = () => {
|
||||
app.unlock(getShapeIds(shapes));
|
||||
};
|
||||
|
||||
+2
-2
@@ -44,12 +44,12 @@ interface LineStyleProps {
|
||||
onStrokeWidthChange: (width: StrokeWidth) => void;
|
||||
}
|
||||
|
||||
export const LineStyle: FC<LineStyleProps> = ({
|
||||
export const LineStyle = ({
|
||||
strokeStyle,
|
||||
onStrokeStyleChange,
|
||||
strokeWidth,
|
||||
onStrokeWidthChange,
|
||||
}) => {
|
||||
}: LineStyleProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<Title>Stroke Style</Title>
|
||||
|
||||
+2
-2
@@ -24,10 +24,10 @@ interface BorderColorConfigProps {
|
||||
shapes: TDShape[];
|
||||
}
|
||||
|
||||
export const StrokeLineStyleConfig: FC<BorderColorConfigProps> = ({
|
||||
export const StrokeLineStyleConfig = ({
|
||||
app,
|
||||
shapes,
|
||||
}) => {
|
||||
}: BorderColorConfigProps) => {
|
||||
const strokeStyle = _getStrokeStyle(shapes);
|
||||
const strokeWidth = _getStrokeWidth(shapes);
|
||||
const setStrokeLineStyle = (style: DashStyle) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { FC, ReactNode } from 'react';
|
||||
|
||||
export const ContextMenu: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
export const ContextMenu = ({ children }: { children: ReactNode }) => {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
@@ -26,11 +26,11 @@ const formatColors = (colors: ColorValue[]): ColorObject[] => {
|
||||
});
|
||||
};
|
||||
|
||||
export const Palette: FC<PaletteProps> = ({
|
||||
export const Palette = ({
|
||||
colors: propColors,
|
||||
selected,
|
||||
onSelect,
|
||||
}) => {
|
||||
}: PaletteProps) => {
|
||||
const colorObjects = useMemo(() => formatColors(propColors), [propColors]);
|
||||
return (
|
||||
<Container>
|
||||
|
||||
@@ -33,7 +33,7 @@ const shapes = [
|
||||
|
||||
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 [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
||||
|
||||
@@ -69,7 +69,7 @@ const shapes = [
|
||||
|
||||
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 [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
Tooltip,
|
||||
PopoverContainer,
|
||||
IconButton,
|
||||
useTheme,
|
||||
} from '@toeverything/components/ui';
|
||||
import {
|
||||
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 isToolLocked = app.useStore(toolLockedSelector);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<PopoverContainer
|
||||
@@ -105,7 +107,8 @@ export const ToolsPanel: FC<{ app: TldrawApp }> = ({ app }) => {
|
||||
style={{
|
||||
color:
|
||||
activeTool === type
|
||||
? 'blue'
|
||||
? theme.affine.palette
|
||||
.primary
|
||||
: '',
|
||||
}}
|
||||
onClick={() => {
|
||||
|
||||
@@ -9,13 +9,13 @@ interface PenProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const Pen: FC<PenProps> = ({
|
||||
export const Pen = ({
|
||||
name,
|
||||
icon,
|
||||
primaryColor,
|
||||
secondaryColor,
|
||||
onClick,
|
||||
}) => {
|
||||
}: PenProps) => {
|
||||
return (
|
||||
<Tooltip content={name}>
|
||||
<StyledIconButton
|
||||
|
||||
@@ -105,7 +105,7 @@ const PENCIL_CONFIGS_MAP = PENCIL_CONFIGS.reduce<
|
||||
return acc;
|
||||
}, {} 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 chosenPen =
|
||||
PENCIL_CONFIGS.find(config => config.key === appCurrentTool) ||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { MiniMap } from './mini-map';
|
||||
const zoomSelector = (s: TDSnapshot) =>
|
||||
s.document.pageStates[s.appState.currentPageId].camera.zoom;
|
||||
|
||||
export const ZoomBar: FC = () => {
|
||||
export const ZoomBar = () => {
|
||||
const app = useTldrawApp();
|
||||
const zoom = app.useStore(zoomSelector);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ const getScaleToMap = (width: number, height: number) => {
|
||||
return scaleWidth > scaleHeight ? scaleWidth : scaleHeight;
|
||||
};
|
||||
|
||||
export const MiniMap: FC = () => {
|
||||
export const MiniMap = () => {
|
||||
const app = useTldrawApp();
|
||||
const page = app.useStore(s => s.document.pages[s.appState.currentPageId]);
|
||||
const pageState = app.useStore(
|
||||
|
||||
@@ -6,13 +6,13 @@ interface SimplifiedShapeProps extends TLBounds {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const SimplifiedShape: FC<SimplifiedShapeProps> = ({
|
||||
export const SimplifiedShape = ({
|
||||
onClick,
|
||||
width,
|
||||
height,
|
||||
minX,
|
||||
minY,
|
||||
}) => {
|
||||
}: SimplifiedShapeProps) => {
|
||||
const style: CSSProperties = {
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
|
||||
@@ -8,13 +8,13 @@ interface ViewportProps extends TLBounds {
|
||||
onPan?: (delta: [number, number]) => void;
|
||||
}
|
||||
|
||||
export const Viewport: FC<ViewportProps> = ({
|
||||
export const Viewport = ({
|
||||
onPan,
|
||||
width,
|
||||
height,
|
||||
minX,
|
||||
minY,
|
||||
}) => {
|
||||
}: ViewportProps) => {
|
||||
const style: CSSProperties = {
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
|
||||
@@ -124,10 +124,16 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
||||
);
|
||||
|
||||
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) {
|
||||
app.setEditingText(shape.id);
|
||||
}
|
||||
}, [app, shape.id, editingText, editingId]);
|
||||
}, [app, state, shape.id, editingText, editingId]);
|
||||
|
||||
return (
|
||||
<HTMLContainer ref={ref} {...events}>
|
||||
|
||||
@@ -219,8 +219,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
isPointing = false;
|
||||
|
||||
isForcePanning = false;
|
||||
|
||||
editingStartTime = -1;
|
||||
|
||||
fileSystemHandle: FileSystemHandle | null = null;
|
||||
@@ -262,7 +260,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
constructor(props: TldrawAppCtorProps) {
|
||||
super(
|
||||
TldrawApp.default_state,
|
||||
TldrawApp.defaultState,
|
||||
props.id,
|
||||
TldrawApp.version,
|
||||
(prev, next, prevVersion) => {
|
||||
@@ -326,9 +324,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
);
|
||||
|
||||
this.patchState({
|
||||
...TldrawApp.default_state,
|
||||
...TldrawApp.defaultState,
|
||||
appState: {
|
||||
...TldrawApp.default_state.appState,
|
||||
...TldrawApp.defaultState.appState,
|
||||
status: TDStatus.Idle,
|
||||
},
|
||||
});
|
||||
@@ -1473,13 +1471,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
this.replace_state(
|
||||
{
|
||||
...TldrawApp.default_state,
|
||||
...TldrawApp.defaultState,
|
||||
settings: {
|
||||
...this.state.settings,
|
||||
},
|
||||
document: migrate(document, TldrawApp.version),
|
||||
appState: {
|
||||
...TldrawApp.default_state.appState,
|
||||
...TldrawApp.defaultState.appState,
|
||||
...this.state.appState,
|
||||
currentPageId: Object.keys(document.pages)[0],
|
||||
disableAssets: this.disableAssets,
|
||||
@@ -3913,7 +3911,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
this.isForcePanning = true;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
this.spaceKey = true;
|
||||
break;
|
||||
}
|
||||
@@ -3976,7 +3978,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
this.isForcePanning = false;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning:
|
||||
this.currentTool.type === TDShapeType.HandDraw,
|
||||
},
|
||||
});
|
||||
this.spaceKey = false;
|
||||
break;
|
||||
}
|
||||
@@ -4069,13 +4076,18 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
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)
|
||||
if (!this.isForcePanning)
|
||||
if (!this.useStore.getState().settings.forcePanning)
|
||||
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
||||
};
|
||||
|
||||
onZoom: TLWheelEventHandler = (info, e) => {
|
||||
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.onPointerMove(info, e as unknown as React.PointerEvent);
|
||||
};
|
||||
@@ -4093,7 +4105,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
this.previousPoint = this.currentPoint;
|
||||
this.updateInputs(info, e);
|
||||
if (this.isForcePanning && this.isPointing) {
|
||||
if (this.useStore.getState().settings.forcePanning && this.isPointing) {
|
||||
this.onPan?.(
|
||||
{ ...info, delta: Vec.neg(info.delta) },
|
||||
e as unknown as WheelEvent
|
||||
@@ -4117,20 +4129,23 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
onPointerDown: TLPointerEventHandler = (info, e) => {
|
||||
if (e.buttons === 4) {
|
||||
this.isForcePanning = true;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
} else if (this.isPointing) {
|
||||
return;
|
||||
}
|
||||
this.isPointing = true;
|
||||
this.originPoint = this.getPagePoint(info.point).concat(info.pressure);
|
||||
this.updateInputs(info, e);
|
||||
if (this.isForcePanning) return;
|
||||
if (this.useStore.getState().settings.forcePanning) return;
|
||||
this.currentTool.onPointerDown?.(info, e);
|
||||
};
|
||||
|
||||
onPointerUp: TLPointerEventHandler = (info, e) => {
|
||||
this.isPointing = false;
|
||||
if (!this.shiftKey) this.isForcePanning = false;
|
||||
this.updateInputs(info, e);
|
||||
this.currentTool.onPointerUp?.(info, e);
|
||||
};
|
||||
@@ -4517,7 +4532,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
assets: {},
|
||||
};
|
||||
|
||||
static default_state: TDSnapshot = {
|
||||
static defaultState: TDSnapshot = {
|
||||
settings: {
|
||||
isCadSelectMode: false,
|
||||
isPenMode: false,
|
||||
@@ -4527,6 +4542,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
isSnapping: false,
|
||||
isDebugMode: false,
|
||||
isReadonlyMode: false,
|
||||
forcePanning: false,
|
||||
keepStyleMenuOpen: false,
|
||||
nudgeDistanceLarge: 16,
|
||||
nudgeDistanceSmall: 1,
|
||||
|
||||
@@ -18,34 +18,19 @@ export class HandDrawTool extends BaseTool {
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.app.readOnly) return;
|
||||
if (this.status !== Status.Idle) return;
|
||||
|
||||
this.set_status(Status.Pointing);
|
||||
override onEnter = () => {
|
||||
this.app.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
if (this.app.readOnly) return;
|
||||
const delta = Vec.div(info.delta, this.app.camera.zoom);
|
||||
const prev = this.app.camera.point;
|
||||
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);
|
||||
override onExit = () => {
|
||||
this.app.patchState({
|
||||
settings: {
|
||||
forcePanning: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface TDSnapshot {
|
||||
isPenMode: boolean;
|
||||
isReadonlyMode: boolean;
|
||||
isZoomSnap: boolean;
|
||||
forcePanning: boolean;
|
||||
keepStyleMenuOpen: boolean;
|
||||
nudgeDistanceSmall: number;
|
||||
nudgeDistanceLarge: number;
|
||||
|
||||
@@ -13,6 +13,7 @@ const StyledContainer = styled('div')({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
cursor: 'pointer',
|
||||
paddingLeft: '12px',
|
||||
'&:hover': {
|
||||
background: '#f5f7f8',
|
||||
borderRadius: '5px',
|
||||
@@ -36,11 +37,6 @@ export function CollapsibleTitle(props: CollapsibleTitleProps) {
|
||||
return (
|
||||
<>
|
||||
<StyledContainer onClick={() => setOpen(prev => !prev)}>
|
||||
{open ? (
|
||||
<ArrowDropDownIcon sx={{ color: '#566B7D' }} />
|
||||
) : (
|
||||
<ArrowRightIcon sx={{ color: '#566B7D' }} />
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
color: '#98ACBD',
|
||||
|
||||
@@ -799,11 +799,11 @@ const EditorLeaf = ({ attributes, children, leaf }: any) => {
|
||||
<span {...attributes}>
|
||||
<code
|
||||
style={{
|
||||
backgroundColor: 'rgba(135,131,120,0.15)',
|
||||
borderRadius: '3px',
|
||||
color: '#EB5757',
|
||||
fontSize: '0.875em',
|
||||
padding: '0.25em 0.375em',
|
||||
backgroundColor: '#F2F5F9',
|
||||
borderRadius: '5px',
|
||||
color: '#3A4C5C',
|
||||
padding: '3px 8px',
|
||||
margin: '0 2px',
|
||||
}}
|
||||
>
|
||||
{customChildren}
|
||||
|
||||
@@ -25,7 +25,6 @@ import { ReactEditor } from 'slate-react';
|
||||
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import LinkOffIcon from '@mui/icons-material/LinkOff';
|
||||
import AttachmentIcon from '@mui/icons-material/Attachment';
|
||||
import {
|
||||
MuiTooltip as Tooltip,
|
||||
styled,
|
||||
@@ -39,7 +38,7 @@ import {
|
||||
|
||||
import { getRandomString } from '../utils';
|
||||
import { colors } from '../../colors';
|
||||
|
||||
import { LinkIcon } from '@toeverything/components/icons';
|
||||
export type LinkElement = {
|
||||
type: 'link';
|
||||
url: string;
|
||||
@@ -124,7 +123,7 @@ const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => (
|
||||
))(({ theme }) => ({
|
||||
[`& .${muiTooltipClasses.tooltip}`]: {
|
||||
backgroundColor: '#fff',
|
||||
color: '#4C6275',
|
||||
color: '#3E6FDB',
|
||||
boxShadow: theme.affine.shadows.shadow1,
|
||||
fontSize: '14px',
|
||||
},
|
||||
@@ -419,8 +418,8 @@ export const LinkModal = memo((props: LinkModalProps) => {
|
||||
}}
|
||||
>
|
||||
<div className={styles('linkModalContainerIcon')}>
|
||||
<AttachmentIcon
|
||||
style={{ color: colors.Gray04, fontSize: 16 }}
|
||||
<LinkIcon
|
||||
style={{ fontSize: '16px', marginTop: '2px' }}
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
@@ -505,8 +504,10 @@ const LinkModalContainer = styled('div')(({ theme }) => ({
|
||||
|
||||
const styles = style9.create({
|
||||
linkModalContainerIcon: {
|
||||
display: 'flex',
|
||||
width: '16px',
|
||||
margin: '0 16px 0 4px',
|
||||
color: '#4C6275',
|
||||
},
|
||||
linkModalContainerInput: {
|
||||
flex: '1',
|
||||
@@ -517,6 +518,7 @@ const styles = style9.create({
|
||||
'::-webkit-input-placeholder': {
|
||||
color: '#98acbd',
|
||||
},
|
||||
color: '#4C6275',
|
||||
},
|
||||
linkMask: {
|
||||
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() {
|
||||
return {
|
||||
anchor: this.getStart(),
|
||||
|
||||
@@ -40,10 +40,11 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
||||
(todoValue.length === 1 && !todoValue[0]['text'])
|
||||
);
|
||||
};
|
||||
const BulletLeft = styled('div')(() => ({
|
||||
const BulletLeft = styled('div')(({ theme }) => ({
|
||||
height: '22px',
|
||||
color: theme.affine.typography.body1.color,
|
||||
}));
|
||||
export const BulletView: FC<CreateView> = ({ block, editor }) => {
|
||||
export const BulletView = ({ block, editor }: CreateView) => {
|
||||
// block.remove();
|
||||
const properties = { ...defaultBulletProps, ...block.getProperties() };
|
||||
const textRef = useRef<ExtendedTextUtils>(null);
|
||||
|
||||
@@ -52,6 +52,8 @@ import {
|
||||
BlockPendantProvider,
|
||||
} from '@toeverything/components/editor-core';
|
||||
import { copyToClipboard } from '@toeverything/utils';
|
||||
import { DuplicateIcon } from '@toeverything/components/icons';
|
||||
|
||||
interface CreateCodeView extends CreateView {
|
||||
style9?: StyleWithAtRules;
|
||||
containerClassName?: string;
|
||||
@@ -117,16 +119,27 @@ const CodeBlock = styled('div')(({ theme }) => ({
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
'.copy-block': {
|
||||
padding: '6px 10px',
|
||||
padding: '0px 10px',
|
||||
backgroundColor: '#fff',
|
||||
height: '32px',
|
||||
display: 'flex',
|
||||
width: '90px',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
color: '#4C6275',
|
||||
fontSize: '14px',
|
||||
borderRadius: theme.affine.shape.borderRadius,
|
||||
cursor: 'pointer',
|
||||
svg: {
|
||||
marginRight: '4px',
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
'.cm-focused': {
|
||||
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 langType: string = block.getProperty('lang');
|
||||
const [extensions, setExtensions] = useState<Extension[]>();
|
||||
@@ -187,7 +200,7 @@ export const CodeView: FC<CreateCodeView> = ({ block, editor }) => {
|
||||
</div>
|
||||
<div>
|
||||
<div className="copy-block" onClick={copyCode}>
|
||||
Copy
|
||||
<DuplicateIcon></DuplicateIcon>Copy
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ const Line = styled('div')({
|
||||
backgroundColor: '#e2e8f0',
|
||||
});
|
||||
|
||||
export const DividerView: FC<CreateView> = ({ block, editor }) => {
|
||||
export const DividerView = ({ block, editor }: CreateView) => {
|
||||
const [isSelected, setIsSelected] = useState(false);
|
||||
|
||||
useOnSelect(block.id, (isSelect: boolean) => {
|
||||
|
||||
@@ -13,7 +13,7 @@ const MESSAGES = {
|
||||
};
|
||||
|
||||
type EmbedLinkView = CreateView;
|
||||
export const EmbedLinkView: FC<EmbedLinkView> = props => {
|
||||
export const EmbedLinkView = (props: EmbedLinkView) => {
|
||||
const { block, editor } = props;
|
||||
const [isSelect, setIsSelect] = useState(false);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ const MESSAGES = {
|
||||
interface FigmaView extends CreateView {
|
||||
figmaUrl?: string;
|
||||
}
|
||||
export const FigmaView: FC<FigmaView> = ({ block, editor }) => {
|
||||
export const FigmaView = ({ block, editor }: FigmaView) => {
|
||||
const [figmaUrl, setFigmaUrl] = useState<string>(
|
||||
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 fileInfo = block.getProperty('file') || ({} as FileColumnValue);
|
||||
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_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 RENDER_DELAY_TIME = 100;
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RenderBlock } from '@toeverything/components/editor-core';
|
||||
import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
|
||||
|
||||
export const GridItemRender = function (creator: FC<ChildrenView>) {
|
||||
const GridItem: FC<CreateView> = function (props) {
|
||||
const GridItem = function (props: CreateView) {
|
||||
const { block } = props;
|
||||
const children = (
|
||||
<>
|
||||
|
||||
@@ -16,7 +16,7 @@ type GridHandleProps = {
|
||||
onMouseEnter?: React.MouseEventHandler<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export const GridHandle: FC<GridHandleProps> = function ({
|
||||
export const GridHandle = function ({
|
||||
blockId,
|
||||
editor,
|
||||
enabledAddItem,
|
||||
@@ -25,7 +25,7 @@ export const GridHandle: FC<GridHandleProps> = function ({
|
||||
draggable,
|
||||
alertHandleId,
|
||||
onMouseEnter,
|
||||
}) {
|
||||
}: GridHandleProps) {
|
||||
const [isMouseDown, setIsMouseDown] = useState<boolean>(false);
|
||||
const handleMouseDown: React.MouseEventHandler<HTMLDivElement> = e => {
|
||||
if (draggable) {
|
||||
|
||||
@@ -20,7 +20,7 @@ export function removePercent(str: string) {
|
||||
return str.replace('%', '');
|
||||
}
|
||||
|
||||
export const Grid: FC<CreateView> = function (props) {
|
||||
export const Grid = function (props: CreateView) {
|
||||
const { block, editor } = props;
|
||||
const gridItemMinWidth = editor.configManager.grid.gridItemMinWidth;
|
||||
const [isOnDrag, setIsOnDrag] = useState<boolean>(false);
|
||||
@@ -261,7 +261,7 @@ const GridContainer = styled('div')<{
|
||||
display: 'flex',
|
||||
alignItems: 'stretch',
|
||||
borderRadius: '10px',
|
||||
border: '1px solid #FFF',
|
||||
border: '1px solid transparent',
|
||||
minWidth: `${gridItemMinWidth}%`,
|
||||
[`&:hover .${GRID_ITEM_CONTENT_CLASS_NAME}`]: {
|
||||
borderColor: theme.affine.palette.borderColor,
|
||||
|
||||
@@ -36,6 +36,9 @@ export class GridBlock extends BaseView {
|
||||
}
|
||||
return block.remove();
|
||||
}
|
||||
if (block.childrenIds.length === 0) {
|
||||
return block.remove();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
addNewGroup,
|
||||
LINE_GAP,
|
||||
RecastScene,
|
||||
TAG_GAP,
|
||||
useCurrentView,
|
||||
useOnSelect,
|
||||
} from '@toeverything/components/editor-core';
|
||||
@@ -38,6 +40,7 @@ const GroupActionWrapper = styled('div')(({ theme }) => ({
|
||||
visibility: 'hidden',
|
||||
fontSize: theme.affine.typography.xs.fontSize,
|
||||
color: theme.affine.palette.icons,
|
||||
opacity: 0.6,
|
||||
'.line': {
|
||||
flex: 1,
|
||||
height: '15px',
|
||||
@@ -60,7 +63,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>(
|
||||
({ isSelect, theme }) => ({
|
||||
background: theme.affine.palette.white,
|
||||
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',
|
||||
...(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 [currentView] = useCurrentView();
|
||||
const [groupIsSelect, setGroupIsSelect] = useState(false);
|
||||
|
||||
@@ -2,6 +2,6 @@ import { RenderBlockChildren } from '@toeverything/components/editor-core';
|
||||
import type { CreateView } from '@toeverything/framework/virgo';
|
||||
import { FC } from 'react';
|
||||
|
||||
export const ScenePage: FC<CreateView> = ({ block }) => {
|
||||
export const ScenePage = ({ block }: CreateView) => {
|
||||
return <RenderBlockChildren block={block} />;
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import type { CreateView } from '@toeverything/framework/virgo';
|
||||
import type { TableColumn, TableRow } 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 data_columns = useMemo<TableColumn[]>(() => {
|
||||
return (columns || [])
|
||||
|
||||
@@ -156,7 +156,13 @@ export const CardContainer = (props: CardContainerProps) => {
|
||||
const { kanban } = useKanban();
|
||||
const { containerIds, items: dataSource, activeId } = props;
|
||||
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) => {
|
||||
const items = dataSource[containerId];
|
||||
|
||||
|
||||
@@ -60,6 +60,9 @@ export const CardContext = (props: Props) => {
|
||||
|
||||
const StyledCardContainer = styled('div')`
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
z-index: 1;
|
||||
}
|
||||
&:focus-within {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FC } from 'react';
|
||||
import { CreateView } from '@toeverything/framework/virgo';
|
||||
|
||||
export const GroupDividerView: FC<CreateView> = ({ block, editor }) => {
|
||||
export const GroupDividerView = ({ block, editor }: CreateView) => {
|
||||
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 [imgUrl, set_image_url] = useState<string>();
|
||||
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();
|
||||
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
||||
const [number, set_number] = useState<number>(1);
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
type ExtendedTextUtils,
|
||||
} from '../../components/text-manage';
|
||||
|
||||
export const PageView: FC<CreateView> = ({ block, editor }) => {
|
||||
export const PageView = ({ block, editor }: CreateView) => {
|
||||
const { workspace_id } = useParams();
|
||||
const textRef = useRef<ExtendedTextUtils>(null);
|
||||
const [backLinks, setBackLinks] =
|
||||
@@ -109,12 +109,15 @@ export const PageView: FC<CreateView> = ({ block, editor }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PageTitleBlock = styled('div')({
|
||||
'.title': {
|
||||
fontSize: Theme.typography.page.fontSize,
|
||||
lineHeight: Theme.typography.page.lineHeight,
|
||||
},
|
||||
'.content': {
|
||||
outline: 'none',
|
||||
},
|
||||
const PageTitleBlock = styled('div')(({ theme }) => {
|
||||
return {
|
||||
'.title': {
|
||||
fontSize: theme.affine.typography.page.fontSize,
|
||||
lineHeight: theme.affine.typography.page.lineHeight,
|
||||
fontWeight: theme.affine.typography.page.fontWeight,
|
||||
},
|
||||
'.content': {
|
||||
outline: 'none',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import { CreateView } from '@toeverything/framework/virgo';
|
||||
|
||||
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 [block_content, set_block] =
|
||||
|
||||
@@ -46,15 +46,16 @@ const TextBlock = styled(TextManage)<{ type: string }>(({ theme, type }) => {
|
||||
return {
|
||||
fontSize: textStyleMap.text.fontSize,
|
||||
lineHeight: textStyleMap.text.lineHeight,
|
||||
fontWeight: textStyleMap.text.fontWeight,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
export const TextView: FC<CreateTextView> = ({
|
||||
export const TextView = ({
|
||||
block,
|
||||
editor,
|
||||
containerClassName,
|
||||
}) => {
|
||||
}: CreateTextView) => {
|
||||
const [isSelect, setIsSelect] = useState<boolean>();
|
||||
useOnSelect(block.id, (is_select: boolean) => {
|
||||
setIsSelect(is_select);
|
||||
@@ -112,13 +113,18 @@ export const TextView: FC<CreateTextView> = ({
|
||||
block.id,
|
||||
'end'
|
||||
);
|
||||
const value = [
|
||||
...preNode.getProperty('text').value,
|
||||
...block.getProperty('text').value,
|
||||
];
|
||||
await preNode.setProperty('text', {
|
||||
value,
|
||||
});
|
||||
if (
|
||||
block.getProperty('text').value[0] &&
|
||||
block.getProperty('text').value[0]?.text !== ''
|
||||
) {
|
||||
const value = [
|
||||
...preNode.getProperty('text').value,
|
||||
...block.getProperty('text').value,
|
||||
];
|
||||
await preNode.setProperty('text', {
|
||||
value,
|
||||
});
|
||||
}
|
||||
await preNode.append(...children);
|
||||
await block.remove();
|
||||
editor.suspend(false);
|
||||
|
||||
@@ -11,7 +11,7 @@ const INITIAL_LIST: MenuItem[] = [];
|
||||
const MESSAGES = {
|
||||
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);
|
||||
useEffect(() => {
|
||||
const sync_toc = async () => {
|
||||
|
||||
@@ -8,20 +8,20 @@ interface CheckBoxProps {
|
||||
onChange: (checked: boolean) => void;
|
||||
}
|
||||
|
||||
export const CheckBox: FC<CheckBoxProps> = ({
|
||||
size = '16px',
|
||||
height = '23px',
|
||||
export const CheckBox = ({
|
||||
size = 16,
|
||||
height = 23,
|
||||
checked,
|
||||
onChange,
|
||||
}) => {
|
||||
}: CheckBoxProps) => {
|
||||
const dynamic_style = useMemo(
|
||||
() => ({
|
||||
height: {
|
||||
height,
|
||||
height: `${height}px`,
|
||||
},
|
||||
size: {
|
||||
width: size,
|
||||
height: size,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
},
|
||||
}),
|
||||
[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 text_ref = useRef<ExtendedTextUtils>(null);
|
||||
|
||||
@@ -150,6 +150,7 @@ const TodoBlock = styled('div')({
|
||||
display: 'flex',
|
||||
'.checkBoxContainer': {
|
||||
marginRight: '4px',
|
||||
padding: '0 4px',
|
||||
height: '22px',
|
||||
},
|
||||
'.textContainer': {
|
||||
|
||||
@@ -10,7 +10,7 @@ const _messages = {
|
||||
};
|
||||
|
||||
type YoutubeView = CreateView;
|
||||
export const YoutubeView: FC<YoutubeView> = ({ block }) => {
|
||||
export const YoutubeView = ({ block }: YoutubeView) => {
|
||||
const [youtubeUrl, setYoutubeUrl] = useState<string>(
|
||||
block.getProperty('embedLink')?.value
|
||||
);
|
||||
|
||||
@@ -7,13 +7,13 @@ type BlockContainerProps = Parameters<typeof Container>[0] & {
|
||||
editor: BlockEditor;
|
||||
};
|
||||
|
||||
export const BlockContainer: FC<BlockContainerProps> = function ({
|
||||
export const BlockContainer = function ({
|
||||
block,
|
||||
children,
|
||||
className,
|
||||
editor,
|
||||
...restProps
|
||||
}) {
|
||||
}: BlockContainerProps) {
|
||||
return (
|
||||
<Container
|
||||
className={`${className || ''} block_container`}
|
||||
@@ -27,5 +27,6 @@ export const BlockContainer: FC<BlockContainerProps> = function ({
|
||||
export const Container = styled('div')<{ selected: boolean }>(
|
||||
({ selected, theme }) => ({
|
||||
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 on_resize_end = (e: any, data: any) => {
|
||||
block.setProperty('image_style', data.size);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { styled } from '@toeverything/components/ui';
|
||||
/**
|
||||
* Indent rendering child nodes
|
||||
*/
|
||||
export const IndentWrapper: FC<PropsWithChildren> = props => {
|
||||
export const IndentWrapper = (props: PropsWithChildren) => {
|
||||
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
|
||||
};
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ErrorBoundary } from '@toeverything/utils';
|
||||
// onChange: () => void;
|
||||
// }
|
||||
|
||||
export const Editable: FC = () => {
|
||||
export const Editable = () => {
|
||||
const editor = useMemo(() => withReact(createEditor()), []);
|
||||
return (
|
||||
<ErrorBoundary
|
||||
|
||||
@@ -156,7 +156,7 @@ const LoadingContiner = () => {
|
||||
return <Loading>loading...</Loading>;
|
||||
};
|
||||
|
||||
export const SourceView: FC<Props> = props => {
|
||||
export const SourceView = (props: Props) => {
|
||||
const { link, isSelected, block, editorElement } = props;
|
||||
const src = formatUrl(link);
|
||||
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
||||
|
||||
@@ -5,6 +5,7 @@ export const List = styled('div')(({ theme }) => ({
|
||||
'.checkBoxContainer': {
|
||||
marginRight: '4px',
|
||||
lineHeight: theme.affine.typography.body1.lineHeight,
|
||||
color: theme.affine.typography.body1.color,
|
||||
},
|
||||
'.textContainer': {
|
||||
flex: 1,
|
||||
|
||||
@@ -64,8 +64,13 @@ export const DEFAULT_RENDER_CELL: CustomCell = ({ value }) => {
|
||||
return <span>{value ? String(value) : '--'}</span>;
|
||||
};
|
||||
|
||||
const Cell: FC<GridChildComponentProps<TableData>> = memo(
|
||||
({ data, rowIndex, columnIndex, style }) => {
|
||||
const Cell = memo(
|
||||
({
|
||||
data,
|
||||
rowIndex,
|
||||
columnIndex,
|
||||
style,
|
||||
}: GridChildComponentProps<TableData>) => {
|
||||
const column = data.columns[columnIndex];
|
||||
const row = data.rows[rowIndex];
|
||||
const is_first_column = columnIndex === 0;
|
||||
@@ -89,18 +94,17 @@ const Cell: FC<GridChildComponentProps<TableData>> = memo(
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
areEqual
|
||||
}
|
||||
);
|
||||
|
||||
export const BasicTable: FC<BasicTableProps> = ({
|
||||
export const BasicTable = ({
|
||||
columns,
|
||||
rows,
|
||||
headerHeight = DEFAULT_ROW_HEIGHT,
|
||||
rowKey,
|
||||
border = true,
|
||||
renderCell = DEFAULT_RENDER_CELL,
|
||||
}) => {
|
||||
}: BasicTableProps) => {
|
||||
const container_ref = useRef<HTMLDivElement>();
|
||||
const [table_width, set_table_width] = useState(0);
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ import type { CellProps } from '../types';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const CheckBoxCell: FC<CellProps<BooleanColumnValue>> = ({
|
||||
export const CheckBoxCell = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
}: CellProps<BooleanColumnValue>) => {
|
||||
return (
|
||||
<Checkbox
|
||||
checked={value?.value}
|
||||
|
||||
@@ -9,7 +9,7 @@ import type { CellProps } from './types';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
const DefaultCell: FC<CellProps> = ({ onChange, ...props }) => {
|
||||
const DefaultCell = ({ onChange, ...props }: CellProps) => {
|
||||
return <DEFAULT_RENDER_CELL {...props} />;
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ interface CustomCellProps extends TableCustomCellProps<unknown> {
|
||||
onChange: (data: TableCustomCellProps<unknown>) => void;
|
||||
}
|
||||
|
||||
export const CustomCell: FC<CustomCellProps> = props => {
|
||||
export const CustomCell = (props: CustomCellProps) => {
|
||||
const View =
|
||||
props.rowIndex === 0
|
||||
? DefaultCell
|
||||
|
||||
@@ -8,11 +8,11 @@ import type { CellProps } from '../types';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
export const SelectCell: FC<CellProps<EnumColumnValue>> = ({
|
||||
export const SelectCell = ({
|
||||
value,
|
||||
column,
|
||||
onChange,
|
||||
}) => {
|
||||
}: CellProps<EnumColumnValue>) => {
|
||||
const options = useMemo(() => {
|
||||
if (isEnumColumn(column.columnConfig)) {
|
||||
return column.columnConfig.options.map(option => {
|
||||
|
||||
@@ -6,7 +6,7 @@ interface TableProps extends BasicTableProps {
|
||||
addon?: ReactNode;
|
||||
}
|
||||
|
||||
export const Table: FC<TableProps> = ({ addon, ...props }) => {
|
||||
export const Table = ({ addon, ...props }: TableProps) => {
|
||||
return (
|
||||
<div>
|
||||
{addon}
|
||||
|
||||
@@ -39,16 +39,22 @@ export type ExtendedTextUtils = SlateUtils & {
|
||||
};
|
||||
const TextBlockContainer = styled(Text)(({ theme }) => ({
|
||||
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) => {
|
||||
let should_include = false;
|
||||
let shouldInclude = false;
|
||||
return arr.filter(block => {
|
||||
if (block === p || block === q) {
|
||||
should_include = !should_include;
|
||||
shouldInclude = !shouldInclude;
|
||||
return true;
|
||||
} else {
|
||||
return should_include;
|
||||
return shouldInclude;
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -112,11 +118,8 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
(ref as MutableRefObject<ExtendedTextUtils>) || defaultRef;
|
||||
|
||||
const properties = block.getProperties();
|
||||
// const [is_select, set_is_select] = useState<boolean>();
|
||||
// useOnSelect(block.id, (is_select: boolean) => {
|
||||
// set_is_select(is_select);
|
||||
// });
|
||||
const on_text_view_set_selection = (selection: Range | Point) => {
|
||||
|
||||
const onTextViewSetSelection = (selection: Range | Point) => {
|
||||
if (selection instanceof Point) {
|
||||
//do some thing
|
||||
} else {
|
||||
@@ -125,18 +128,18 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
};
|
||||
|
||||
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
|
||||
const on_text_view_active = useCallback(
|
||||
const onTextViewActive = useCallback(
|
||||
(point: CursorTypes) => {
|
||||
// TODO code to be optimized
|
||||
if (textRef.current) {
|
||||
const end_selection = textRef.current.getEndSelection();
|
||||
const start_selection = textRef.current.getStartSelection();
|
||||
const endSelection = textRef.current.getEndSelection();
|
||||
const startSelection = textRef.current.getStartSelection();
|
||||
if (point === 'start') {
|
||||
textRef.current.setSelection(start_selection);
|
||||
textRef.current.setSelection(startSelection);
|
||||
return;
|
||||
}
|
||||
if (point === 'end') {
|
||||
textRef.current.setSelection(end_selection);
|
||||
textRef.current.setSelection(endSelection);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
@@ -151,24 +154,24 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
} else {
|
||||
blockTop = blockDomStyle.top + 5;
|
||||
}
|
||||
const end_position = ReactEditor.toDOMRange(
|
||||
const endPosition = ReactEditor.toDOMRange(
|
||||
textRef.current.editor,
|
||||
end_selection
|
||||
endSelection
|
||||
)
|
||||
.getClientRects()
|
||||
.item(0);
|
||||
const start_position = ReactEditor.toDOMRange(
|
||||
const startPosition = ReactEditor.toDOMRange(
|
||||
textRef.current.editor,
|
||||
start_selection
|
||||
startSelection
|
||||
)
|
||||
.getClientRects()
|
||||
.item(0);
|
||||
if (end_position.left <= point.x) {
|
||||
textRef.current.setSelection(end_selection);
|
||||
if (endPosition.left <= point.x) {
|
||||
textRef.current.setSelection(endSelection);
|
||||
return;
|
||||
}
|
||||
if (start_position.left >= point.x) {
|
||||
textRef.current.setSelection(start_selection);
|
||||
if (startPosition.left >= point.x) {
|
||||
textRef.current.setSelection(startSelection);
|
||||
return;
|
||||
}
|
||||
let range: globalThis.Range;
|
||||
@@ -186,7 +189,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
range = document.createRange();
|
||||
range.setStart(caret.offsetNode, caret.offset);
|
||||
}
|
||||
const slate_rang = ReactEditor.toSlateRange(
|
||||
const slateRang = ReactEditor.toSlateRange(
|
||||
textRef.current.editor,
|
||||
range,
|
||||
{
|
||||
@@ -194,19 +197,19 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
suppressThrow: true,
|
||||
}
|
||||
);
|
||||
textRef.current.setSelection(slate_rang);
|
||||
textRef.current.setSelection(slateRang);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('e: ', e);
|
||||
textRef.current.setSelection(end_selection);
|
||||
textRef.current.setSelection(endSelection);
|
||||
}
|
||||
}
|
||||
},
|
||||
[textRef]
|
||||
);
|
||||
|
||||
useOnSelectActive(block.id, on_text_view_active);
|
||||
useOnSelectSetSelection<'Range'>(block.id, on_text_view_set_selection);
|
||||
useOnSelectActive(block.id, onTextViewActive);
|
||||
useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection);
|
||||
|
||||
useEffect(() => {
|
||||
if (textRef.current) {
|
||||
@@ -232,17 +235,17 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
(block.id === lastSelectNodeId && type === 'Range') ||
|
||||
(type === 'Range' && info)
|
||||
) {
|
||||
on_text_view_active('end');
|
||||
onTextViewActive('end');
|
||||
} else {
|
||||
on_text_view_active('start');
|
||||
onTextViewActive('start');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
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,
|
||||
textStyle
|
||||
) => {
|
||||
@@ -263,39 +266,34 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
});
|
||||
}
|
||||
};
|
||||
const get_now_and_pre_rang_position = () => {
|
||||
window.getSelection().getRangeAt(0);
|
||||
// const now_range =
|
||||
// editor.selectionManager.currentSelectInfo?.browserSelection.getRangeAt(
|
||||
// 0
|
||||
// );
|
||||
const now_range = window.getSelection().getRangeAt(0);
|
||||
let pre_position = null;
|
||||
const now_position = now_range.getClientRects().item(0);
|
||||
const getNowAndPreRangPosition = () => {
|
||||
const nowRange = window.getSelection().getRangeAt(0);
|
||||
let prePosition = null;
|
||||
const nowPosition = nowRange.getClientRects().item(0);
|
||||
try {
|
||||
if (now_range.startOffset !== 0) {
|
||||
const pre_rang = document.createRange();
|
||||
pre_rang.setStart(
|
||||
now_range.startContainer,
|
||||
now_range.startOffset + 1
|
||||
if (nowRange.startOffset !== 0) {
|
||||
const preRang = document.createRange();
|
||||
preRang.setStart(
|
||||
nowRange.startContainer,
|
||||
nowRange.startOffset + 1
|
||||
);
|
||||
pre_rang.setEnd(
|
||||
now_range.endContainer,
|
||||
now_range.endOffset + 1
|
||||
preRang.setEnd(
|
||||
nowRange.endContainer,
|
||||
nowRange.endOffset + 1
|
||||
);
|
||||
pre_position = pre_rang.getClientRects().item(0);
|
||||
prePosition = preRang.getClientRects().item(0);
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e);
|
||||
}
|
||||
return { nowPosition: now_position, prePosition: pre_position };
|
||||
return { nowPosition: nowPosition, prePosition: prePosition };
|
||||
};
|
||||
|
||||
const onKeyboardUp = (event: React.KeyboardEvent<Element>) => {
|
||||
// if default event is prevented do noting
|
||||
// if U want to disable up/down/enter use capture event for preventing
|
||||
if (!event.isDefaultPrevented()) {
|
||||
const positions = get_now_and_pre_rang_position();
|
||||
const positions = getNowAndPreRangPosition();
|
||||
const prePosition = positions.prePosition;
|
||||
const nowPosition = positions.nowPosition;
|
||||
if (prePosition) {
|
||||
@@ -336,7 +334,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
// editor.selectionManager.activeNextNode(block.id, 'start');
|
||||
// return;
|
||||
if (!event.isDefaultPrevented()) {
|
||||
const positions = get_now_and_pre_rang_position();
|
||||
const positions = getNowAndPreRangPosition();
|
||||
const prePosition = positions.prePosition;
|
||||
const nowPosition = positions.nowPosition;
|
||||
// Create the last element range of slate_editor
|
||||
@@ -392,7 +390,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
return false;
|
||||
}
|
||||
};
|
||||
const on_select_all = () => {
|
||||
const onSelectAll = () => {
|
||||
const isSelectAll =
|
||||
textRef.current.isEmpty() || textRef.current.isSelectAll();
|
||||
if (isSelectAll) {
|
||||
@@ -402,22 +400,20 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
return false;
|
||||
};
|
||||
|
||||
const on_undo = () => {
|
||||
const onUndo = () => {
|
||||
editor.undo();
|
||||
};
|
||||
|
||||
const on_redo = () => {
|
||||
const onRedo = () => {
|
||||
editor.redo();
|
||||
};
|
||||
|
||||
const on_keyboard_esc = () => {
|
||||
const onKeyboardEsc = () => {
|
||||
if (editor.selectionManager.getSelectedNodesIds().length === 0) {
|
||||
const active_node_id =
|
||||
const activeNodeId =
|
||||
editor.selectionManager.getActivatedNodeId();
|
||||
if (active_node_id) {
|
||||
editor.selectionManager.setSelectedNodesIds([
|
||||
active_node_id,
|
||||
]);
|
||||
if (activeNodeId) {
|
||||
editor.selectionManager.setSelectedNodesIds([activeNodeId]);
|
||||
ReactEditor.blur(textRef.current.editor);
|
||||
}
|
||||
} 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) {
|
||||
const activeId = editor.selectionManager.getActivatedNodeId();
|
||||
if (activeId === block.id) {
|
||||
@@ -474,16 +470,16 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
|
||||
className={`${otherOptions.className}`}
|
||||
currentValue={properties.text.value}
|
||||
textStyle={properties.textStyle}
|
||||
handleChange={on_text_change}
|
||||
handleChange={onTextChange}
|
||||
handleUp={onKeyboardUp}
|
||||
handleDown={onKeyboardDown}
|
||||
handleLeft={onKeyboardLeft}
|
||||
handleRight={onKeyboardRight}
|
||||
handleSelectAll={on_select_all}
|
||||
handleMouseDown={on_shift_click}
|
||||
handleUndo={on_undo}
|
||||
handleRedo={on_redo}
|
||||
handleEsc={on_keyboard_esc}
|
||||
handleSelectAll={onSelectAll}
|
||||
handleMouseDown={onShiftClick}
|
||||
handleUndo={onUndo}
|
||||
handleRedo={onRedo}
|
||||
handleEsc={onKeyboardEsc}
|
||||
{...otherOptions}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -71,7 +71,7 @@ const UploadBox = styled('div')<{ isSelected: boolean }>(
|
||||
);
|
||||
|
||||
const button_styles: SxProps = { width: '60%', fontSize: '12px' };
|
||||
export const Upload: FC<Props> = props => {
|
||||
export const Upload = (props: Props) => {
|
||||
const {
|
||||
fileChange,
|
||||
size,
|
||||
|
||||
@@ -27,7 +27,7 @@ const defaultConfig: WithChildrenConfig = {
|
||||
const TreeView = forwardRef<
|
||||
HTMLDivElement,
|
||||
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
|
||||
>(({ lastItem, children, onClick, ...restProps }, ref) => {
|
||||
>(({ lastItem = false, children, onClick, ...restProps }, ref) => {
|
||||
return (
|
||||
<TreeWrapper ref={ref} {...restProps}>
|
||||
<StyledTreeView>
|
||||
@@ -155,8 +155,11 @@ const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
|
||||
const Children = Wrapper;
|
||||
|
||||
const TREE_COLOR = '#D5DFE6';
|
||||
// TODO determine the position of the horizontal line by the type of the item
|
||||
const ITEM_POINT_HEIGHT = '12.5px'; // '50%'
|
||||
// adjust left and right margins of the the tree line
|
||||
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')({
|
||||
position: 'relative',
|
||||
@@ -164,7 +167,7 @@ const TreeWrapper = styled('div')({
|
||||
|
||||
const StyledTreeView = styled('div')({
|
||||
position: 'absolute',
|
||||
left: '-21px',
|
||||
left: TREE_LINE_LEFT_OFFSET,
|
||||
height: '100%',
|
||||
});
|
||||
|
||||
@@ -183,7 +186,7 @@ const Line = styled('div')({
|
||||
|
||||
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||
width: '1px',
|
||||
height: last ? ITEM_POINT_HEIGHT : '100%',
|
||||
height: last ? TREE_LINE_TOP_OFFSET : '100%',
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
transform: 'translate(-50%, 0)',
|
||||
@@ -192,11 +195,11 @@ const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||
}));
|
||||
|
||||
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||
width: '16px',
|
||||
width: TREE_LINE_WIDTH,
|
||||
height: '1px',
|
||||
paddingLeft: 0,
|
||||
paddingRight: 0,
|
||||
top: ITEM_POINT_HEIGHT,
|
||||
top: TREE_LINE_TOP_OFFSET,
|
||||
transform: 'translate(0, -50%)',
|
||||
opacity: last ? 0 : 'unset',
|
||||
}));
|
||||
@@ -204,7 +207,8 @@ const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
|
||||
const Collapsed = styled('div')({
|
||||
cursor: 'pointer',
|
||||
display: 'inline-block',
|
||||
color: '#B9CAD5',
|
||||
color: '#98ACBD',
|
||||
padding: '8px',
|
||||
});
|
||||
|
||||
const LastItemRadius = styled('div')({
|
||||
@@ -212,9 +216,9 @@ const LastItemRadius = styled('div')({
|
||||
position: 'absolute',
|
||||
left: '-0.5px',
|
||||
top: 0,
|
||||
height: ITEM_POINT_HEIGHT,
|
||||
height: TREE_LINE_TOP_OFFSET,
|
||||
bottom: '50%',
|
||||
width: '16px',
|
||||
width: TREE_LINE_WIDTH,
|
||||
borderWidth: '1px',
|
||||
borderStyle: 'solid',
|
||||
borderLeftColor: TREE_COLOR,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { BlockEditor } from './editor';
|
||||
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 { EditorProvider } from './Contexts';
|
||||
import { SelectionRect, SelectionRef } from './Selection';
|
||||
@@ -24,11 +24,11 @@ interface RenderRootProps {
|
||||
const MAX_PAGE_WIDTH = 5000;
|
||||
export const MIN_PAGE_WIDTH = 1480;
|
||||
|
||||
export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
||||
export const RenderRoot = ({
|
||||
editor,
|
||||
editorElement,
|
||||
children,
|
||||
}) => {
|
||||
}: PropsWithChildren<RenderRootProps>) => {
|
||||
const selectionRef = useRef<SelectionRef>(null);
|
||||
const triggeredBySelect = useRef(false);
|
||||
const [pageWidth, setPageWidth] = useState<number>(MIN_PAGE_WIDTH);
|
||||
@@ -102,6 +102,12 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
||||
editor.getHooks().onRootNodeMouseLeave(event);
|
||||
};
|
||||
|
||||
const onContextmenu = (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
) => {
|
||||
selectionRef.current?.onContextmenu(event);
|
||||
};
|
||||
|
||||
const onKeyDown: React.KeyboardEventHandler<HTMLDivElement> = event => {
|
||||
// IMP move into keyboard managers?
|
||||
editor.getHooks().onRootNodeKeyDown(event);
|
||||
@@ -146,6 +152,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
||||
};
|
||||
|
||||
const onDrop = (event: React.DragEvent<Element>) => {
|
||||
event.preventDefault();
|
||||
editor.dragDropManager.handlerEditorDrop(event);
|
||||
editor.getHooks().onRootNodeDrop(event);
|
||||
};
|
||||
@@ -165,6 +172,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
||||
onMouseUp={onMouseUp}
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseOut={onMouseOut}
|
||||
onContextMenu={onContextmenu}
|
||||
onKeyDown={onKeyDown}
|
||||
onKeyDownCapture={onKeyDownCapture}
|
||||
onKeyUp={onKeyUp}
|
||||
|
||||
@@ -29,6 +29,9 @@ export type SelectionRef = {
|
||||
onMouseDown: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
onMouseMove: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
onMouseUp: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||
onContextmenu: (
|
||||
event: React.MouseEvent<HTMLDivElement, MouseEvent>
|
||||
) => void;
|
||||
};
|
||||
|
||||
const getFixedPoint = (
|
||||
@@ -207,10 +210,17 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
||||
scrollManager.stopAutoScroll();
|
||||
};
|
||||
|
||||
const onContextmenu = () => {
|
||||
if (mouseType.current === 'down') {
|
||||
onMouseUp();
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
onMouseDown,
|
||||
onMouseMove,
|
||||
onMouseUp,
|
||||
onContextmenu,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
@@ -223,7 +233,8 @@ export const SelectionRect = forwardRef<SelectionRef, SelectionProps>(
|
||||
startPointRef.current &&
|
||||
endPointRef.current &&
|
||||
scrollManager.scrollContainer &&
|
||||
scrollContainerRect.current
|
||||
scrollContainerRect.current &&
|
||||
mouseType.current === 'down'
|
||||
) {
|
||||
const xSign = DIRECTION_VALUE_MAP[direction[0]] || 0;
|
||||
const ySign = DIRECTION_VALUE_MAP[direction[1]] || 0;
|
||||
|
||||
@@ -4,7 +4,11 @@ import {
|
||||
Popover,
|
||||
type PopoverProps,
|
||||
PopperHandler,
|
||||
Tag,
|
||||
type PopperProps,
|
||||
} from '@toeverything/components/ui';
|
||||
import { TagsIcon } from '@toeverything/components/icons';
|
||||
|
||||
import { CreatePendantPanel } from './pendant-operation-panel';
|
||||
import { IconButton } from './StyledComponent';
|
||||
import { AsyncBlock } from '../editor';
|
||||
@@ -13,17 +17,21 @@ type Props = {
|
||||
block: AsyncBlock;
|
||||
onSure?: () => void;
|
||||
iconStyle?: CSSProperties;
|
||||
useAddIcon?: boolean;
|
||||
} & Omit<PopoverProps, 'content'>;
|
||||
export const AddPendantPopover = ({
|
||||
block,
|
||||
onSure,
|
||||
iconStyle,
|
||||
useAddIcon = true,
|
||||
...popoverProps
|
||||
}: Props) => {
|
||||
const popoverHandlerRef = useRef<PopperHandler>();
|
||||
const popperRef = useRef<any>();
|
||||
return (
|
||||
<Popover
|
||||
ref={popoverHandlerRef}
|
||||
popperHandlerRef={popoverHandlerRef}
|
||||
popperRef={popperRef}
|
||||
content={
|
||||
<CreatePendantPanel
|
||||
block={block}
|
||||
@@ -31,6 +39,9 @@ export const AddPendantPopover = ({
|
||||
popoverHandlerRef.current?.setVisible(false);
|
||||
onSure?.();
|
||||
}}
|
||||
onTypeChange={() => {
|
||||
popperRef.current?.update?.();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
placement="bottom-start"
|
||||
@@ -38,9 +49,25 @@ export const AddPendantPopover = ({
|
||||
style={{ padding: 0 }}
|
||||
{...popoverProps}
|
||||
>
|
||||
<IconButton style={{ marginRight: 12, ...iconStyle }}>
|
||||
<Add sx={{ fontSize: 14 }} />
|
||||
</IconButton>
|
||||
{useAddIcon ? (
|
||||
<IconButton style={{ marginRight: 12, ...iconStyle }}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { styled } from '@toeverything/components/ui';
|
||||
import type { AsyncBlock } from '../editor';
|
||||
import { PendantPopover } from './pendant-popover';
|
||||
import { PendantRender } from './pendant-render';
|
||||
import { useRef } from 'react';
|
||||
import { getRecastItemValue, useRecastBlockMeta } from '../recast-block';
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
@@ -10,17 +12,31 @@ interface BlockTagProps {
|
||||
block: AsyncBlock;
|
||||
}
|
||||
|
||||
export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
||||
export const BlockPendantProvider = ({
|
||||
block,
|
||||
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 (
|
||||
<Container>
|
||||
{children}
|
||||
|
||||
<PendantPopover block={block}>
|
||||
<StyledTriggerLine />
|
||||
</PendantPopover>
|
||||
{showTriggerLine ? (
|
||||
<StyledPendantContainer ref={triggerRef}>
|
||||
<PendantPopover
|
||||
block={block}
|
||||
container={triggerRef.current}
|
||||
>
|
||||
<StyledTriggerLine />
|
||||
</PendantPopover>
|
||||
</StyledPendantContainer>
|
||||
) : null}
|
||||
|
||||
<PendantRender block={block} />
|
||||
</Container>
|
||||
@@ -28,7 +44,7 @@ export const BlockPendantProvider: FC<PropsWithChildren<BlockTagProps>> = ({
|
||||
};
|
||||
|
||||
export const LINE_GAP = 16;
|
||||
const TAG_GAP = 4;
|
||||
export const TAG_GAP = 4;
|
||||
|
||||
const StyledTriggerLine = styled('div')({
|
||||
padding: `${TAG_GAP}px 0`,
|
||||
@@ -43,10 +59,12 @@ const StyledTriggerLine = styled('div')({
|
||||
width: '100%',
|
||||
height: '2px',
|
||||
background: '#dadada',
|
||||
display: 'none',
|
||||
display: 'flex',
|
||||
position: 'absolute',
|
||||
left: '0',
|
||||
top: '4px',
|
||||
transition: 'opacity .2s',
|
||||
opacity: '0',
|
||||
},
|
||||
'::after': {
|
||||
content: "''",
|
||||
@@ -60,18 +78,24 @@ const StyledTriggerLine = styled('div')({
|
||||
transition: 'width .3s',
|
||||
},
|
||||
});
|
||||
|
||||
const Container = styled('div')({
|
||||
position: 'relative',
|
||||
paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`,
|
||||
const StyledPendantContainer = styled('div')({
|
||||
width: '100px',
|
||||
'&:hover': {
|
||||
[StyledTriggerLine.toString()]: {
|
||||
'&::before': {
|
||||
display: 'flex',
|
||||
},
|
||||
[`${StyledTriggerLine}`]: {
|
||||
'&::after': {
|
||||
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, {
|
||||
FC,
|
||||
FunctionComponent,
|
||||
PropsWithChildren,
|
||||
CSSProperties,
|
||||
} from 'react';
|
||||
import React from 'react';
|
||||
import { Tag, type TagProps } from '@toeverything/components/ui';
|
||||
import {
|
||||
DateValue,
|
||||
|
||||
@@ -98,8 +98,8 @@ export const pendantConfig: { [key: string]: PendantConfig } = {
|
||||
},
|
||||
[PendantTypes.Status]: {
|
||||
iconName: IconNames.STATUS,
|
||||
background: ['#C5FBE0', '#FFF5AB', '#FFCECE', '#E3DEFF'],
|
||||
color: ['#05683D', '#896406', '#AF1212', '#511AAB'],
|
||||
background: ['#FFCECE', '#FFF5AB', '#C5FBE0', '#E3DEFF'],
|
||||
color: ['#AF1212', '#896406', '#05683D', '#511AAB'],
|
||||
},
|
||||
[PendantTypes.Select]: {
|
||||
iconName: IconNames.SINGLE_SELECT,
|
||||
|
||||
+4
-2
@@ -29,6 +29,7 @@ export const PendantHistoryPanel = ({
|
||||
|
||||
const [history, setHistory] = useState<RecastBlockValue[]>([]);
|
||||
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
|
||||
const historyPanelRef = useRef<HTMLDivElement>();
|
||||
const { getValueHistory } = getRecastItemValue(block);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,13 +85,13 @@ export const PendantHistoryPanel = ({
|
||||
}, [block, getProperties, groupBlock, recastBlock]);
|
||||
|
||||
return (
|
||||
<StyledPendantHistoryPanel>
|
||||
<StyledPendantHistoryPanel ref={historyPanelRef}>
|
||||
{history.map(item => {
|
||||
const property = getProperty(item.id);
|
||||
return (
|
||||
<Popover
|
||||
key={item.id}
|
||||
ref={ref => {
|
||||
popperHandlerRef={ref => {
|
||||
popoverHandlerRef.current[item.id] = ref;
|
||||
}}
|
||||
placement="bottom-start"
|
||||
@@ -116,6 +117,7 @@ export const PendantHistoryPanel = ({
|
||||
/>
|
||||
}
|
||||
trigger="click"
|
||||
container={historyPanelRef.current}
|
||||
>
|
||||
<PendantTag
|
||||
style={{
|
||||
|
||||
@@ -11,6 +11,7 @@ export type ModifyPanelProps = {
|
||||
iconConfig?: PendantConfig;
|
||||
isStatusSelect?: boolean;
|
||||
property?: RecastMetaProperty;
|
||||
onTypeChange?: (type: PendantTypes) => void;
|
||||
};
|
||||
|
||||
export type ModifyPanelContentProps = {
|
||||
|
||||
+21
-12
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Input, Option, Select, Tooltip } from '@toeverything/components/ui';
|
||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||
import { message, Option, Select } from '@toeverything/components/ui';
|
||||
import { AsyncBlock } from '../../editor';
|
||||
|
||||
import { IconMap, pendantOptions } from '../config';
|
||||
@@ -8,7 +7,6 @@ import { PendantOptions } from '../types';
|
||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||
import {
|
||||
StyledDivider,
|
||||
StyledInputEndAdornment,
|
||||
StyledOperationLabel,
|
||||
StyledOperationTitle,
|
||||
StyledPopoverSubTitle,
|
||||
@@ -18,15 +16,19 @@ import {
|
||||
generateRandomFieldName,
|
||||
generateInitialOptions,
|
||||
getPendantConfigByType,
|
||||
checkPendantForm,
|
||||
} from '../utils';
|
||||
import { FieldTitleInput } from './FieldTitleInput';
|
||||
import { useOnCreateSure } from './hooks';
|
||||
|
||||
export const CreatePendantPanel = ({
|
||||
block,
|
||||
onSure,
|
||||
onTypeChange,
|
||||
}: {
|
||||
block: AsyncBlock;
|
||||
onSure?: () => void;
|
||||
onTypeChange?: (option: PendantOptions) => void;
|
||||
}) => {
|
||||
const [selectedOption, setSelectedOption] = useState<PendantOptions>();
|
||||
const [fieldName, setFieldName] = useState<string>('');
|
||||
@@ -37,6 +39,10 @@ export const CreatePendantPanel = ({
|
||||
setFieldName(generateRandomFieldName(selectedOption.type));
|
||||
}, [selectedOption]);
|
||||
|
||||
useEffect(() => {
|
||||
onTypeChange?.(selectedOption);
|
||||
}, [selectedOption, onTypeChange]);
|
||||
|
||||
return (
|
||||
<StyledPopoverWrapper>
|
||||
<StyledOperationTitle>Add Field</StyledOperationTitle>
|
||||
@@ -67,19 +73,11 @@ export const CreatePendantPanel = ({
|
||||
})}
|
||||
</Select>
|
||||
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
||||
<Input
|
||||
<FieldTitleInput
|
||||
value={fieldName}
|
||||
placeholder="Input your field name here"
|
||||
onChange={e => {
|
||||
setFieldName(e.target.value);
|
||||
}}
|
||||
endAdornment={
|
||||
<Tooltip content="Help info here">
|
||||
<StyledInputEndAdornment>
|
||||
<HelpCenterIcon />
|
||||
</StyledInputEndAdornment>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
{selectedOption ? (
|
||||
<>
|
||||
@@ -98,6 +96,17 @@ export const CreatePendantPanel = ({
|
||||
)}
|
||||
iconConfig={getPendantConfigByType(selectedOption.type)}
|
||||
onSure={async (type, newPropertyItem, newValue) => {
|
||||
const checkResult = checkPendantForm(
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue
|
||||
);
|
||||
|
||||
if (!checkResult.passed) {
|
||||
await message.error(checkResult.message);
|
||||
return;
|
||||
}
|
||||
await onCreateSure({
|
||||
type,
|
||||
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 { Input, Tooltip } from '@toeverything/components/ui';
|
||||
import { HelpCenterIcon } from '@toeverything/components/icons';
|
||||
import { message } from '@toeverything/components/ui';
|
||||
import { PendantModifyPanel } from '../pendant-modify-panel';
|
||||
import type { AsyncBlock } from '../../editor';
|
||||
import {
|
||||
@@ -8,17 +7,16 @@ import {
|
||||
type RecastBlockValue,
|
||||
type RecastMetaProperty,
|
||||
} from '../../recast-block';
|
||||
import { getPendantConfigByType } from '../utils';
|
||||
import { checkPendantForm, getPendantConfigByType } from '../utils';
|
||||
import {
|
||||
StyledPopoverWrapper,
|
||||
StyledOperationLabel,
|
||||
StyledInputEndAdornment,
|
||||
StyledDivider,
|
||||
StyledPopoverContent,
|
||||
StyledPopoverSubTitle,
|
||||
} from '../StyledComponent';
|
||||
import { IconMap, pendantOptions } from '../config';
|
||||
|
||||
import { FieldTitleInput } from './FieldTitleInput';
|
||||
import { useOnUpdateSure } from './hooks';
|
||||
|
||||
type Props = {
|
||||
@@ -63,19 +61,11 @@ export const UpdatePendantPanel = ({
|
||||
</StyledPopoverContent>
|
||||
<StyledOperationLabel>Field Title</StyledOperationLabel>
|
||||
{titleEditable ? (
|
||||
<Input
|
||||
<FieldTitleInput
|
||||
value={fieldName}
|
||||
placeholder="Input your field name here"
|
||||
onChange={e => {
|
||||
setFieldName(e.target.value);
|
||||
}}
|
||||
endAdornment={
|
||||
<Tooltip content="Help info here">
|
||||
<StyledInputEndAdornment>
|
||||
<HelpCenterIcon />
|
||||
</StyledInputEndAdornment>
|
||||
</Tooltip>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<StyledPopoverContent>{property.name}</StyledPopoverContent>
|
||||
@@ -98,6 +88,17 @@ export const UpdatePendantPanel = ({
|
||||
property={property}
|
||||
type={property.type}
|
||||
onSure={async (type, newPropertyItem, newValue) => {
|
||||
const checkResult = checkPendantForm(
|
||||
type,
|
||||
fieldName,
|
||||
newPropertyItem,
|
||||
newValue
|
||||
);
|
||||
|
||||
if (!checkResult.passed) {
|
||||
await message.error(checkResult.message);
|
||||
return;
|
||||
}
|
||||
await onUpdateSure({
|
||||
type,
|
||||
newPropertyItem,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user