Merge branch 'develop' into opti-toc-dev

This commit is contained in:
mitsuha(XiWen TU)
2022-08-24 01:55:20 +08:00
committed by GitHub
30 changed files with 398 additions and 270 deletions
+34 -85
View File
@@ -2,12 +2,13 @@
<b>
<a href="https://affine.pro">AFFiNE.PRO</a><br>
</b>
The Next-Gen Knowledge Base to Replace Notion & Miro.
The Next-Gen Collaborative Knowledge Base
<br>
</h1>
<p align="center">
Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Free to use.
Open-source and privacy-first. <br />
A free replacement for Notion & Miro.
</p>
<div align="center">
@@ -22,9 +23,10 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
<!-- ALL-CONTRIBUTORS-BADGE:END -->
[![affine.pro](https://img.shields.io/static/v1?label=live%20demo&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhpJREFUWEdjZEACtnl3MxgY/0YzMjAaMzAwcCLLUYH9/T/D/7MM/5mXHp6kPANmHiOI4Zx9Xfg3C+tKBob/zlSwiAgjGPey/vkdvneq5luwA+zy7+yhn+Vwv+89NFHFhREU7IyM/6YT4WyqK/n/nymT0Tb/1mFGBkYbqptOhIH/Gf4fYbTLv/2NBgmOCOvBSr6DHPCfWNW0UEe2A2x1uRlakiXBbtpx6jND+7KXZLmPbAdURokzeJjxwi31rrzH8OX7P5IdQbYDtnUoMXBzMMEt7Fj2imH7qU/0cQBy8MNsPHL5K0P13Of0cQB68MNsJScaSI4CHk4mhq3tSnCf3n36k0FZmh3Mn7L+DcPqgx9ICgWSHeBpxsdQESUGtgRk+eqDH+H8O09/MiR3P6atA1qTJRlsdLnhPgYlPOQQCW96wPDi3R+iHUFSCKAHP8wydEeREg0kOQA9+JOgwR1qL8CQEygC9jWp0UCSA+aVysIT3JqDHxgmr38DtlRCiIVhZZ0CPNhB6QDkEGIA0Q4gZAkuxxFyBNEOQA7ml+/+MIQ1PUAxG1kelAhB6YMYQLQDCPmQUAjhcgxRDiDWcEKOxOYIohyQGyjCEGIvANaPLfhhBiNHA6hmBBXNhABRDgCV/aBQAAFQpYMrn4PUgNTCACiXEMoNRDmAkC8okR8UDhjYRumAN8sHvGMCSkAD2jUDOWDAO6ewbDQQ3XMAy/oxKownQR0AAAAASUVORK5CYII=&color=orange&message=→)](https://affine.pro)
[![affine.pro](https://img.shields.io/static/v1?label=live%20demo&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhpJREFUWEdjZEACtnl3MxgY/0YzMjAaMzAwcCLLUYH9/T/D/7MM/5mXHp6kPANmHiOI4Zx9Xfg3C+tKBob/zlSwiAgjGPey/vkdvneq5luwA+zy7+yhn+Vwv+89NFHFhREU7IyM/6YT4WyqK/n/nymT0Tb/1mFGBkYbqptOhIH/Gf4fYbTLv/2NBgmOCOvBSr6DHPCfWNW0UEe2A2x1uRlakiXBbtpx6jND+7KXZLmPbAdURokzeJjxwi31rrzH8OX7P5IdQbYDtnUoMXBzMMEt7Fj2imH7qU/0cQBy8MNsPHL5K0P13Of0cQB68MNsJScaSI4CHk4mhq3tSnCf3n36k0FZmh3Mn7L+DcPqgx9ICgWSHeBpxsdQESUGtgRk+eqDH+H8O09/MiR3P6atA1qTJRlsdLnhPgYlPOQQCW96wPDi3R+iHUFSCKAHP8wydEeREg0kOQA9+JOgwR1qL8CQEygC9jWp0UCSA+aVysIT3JqDHxgmr38DtlRCiIVhZZ0CPNhB6QDkEGIA0Q4gZAkuxxFyBNEOQA7ml+/+MIQ1PUAxG1kelAhB6YMYQLQDCPmQUAjhcgxRDiDWcEKOxOYIohyQGyjCEGIvANaPLfhhBiNHA6hmBBXNhABRDgCV/aBQAAFQpYMrn4PUgNTCACiXEMoNRDmAkC8okR8UDhjYRumAN8sHvGMCSkAD2jUDOWDAO6ewbDQQ3XMAy/oxKownQR0AAAAASUVORK5CYII=&color=orange&message=→)](https://livedemo.affine.pro)
[![stars](https://img.shields.io/github/stars/toeverything/AFFiNE.svg?style=flat&logo=github&colorB=red&label=stars)](https://github.com/toeverything/AFFiNE)
[![All Contributors][all-contributors-badge]](#contributors)
<br/>
[![Node](https://img.shields.io/badge/node->=16.0-success)](https://www.typescriptlang.org/)
[![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/)
[![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/)
@@ -35,11 +37,11 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
<p align="center">
<a href="http://affine.pro"><img src="https://img.shields.io/badge/-AFFiNE-06449d?style=social&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAMAAAAPkIrYAAAAP1BMVEU8b9w8b9w+b947cNw7b9w6b908b909b9w8b9w7b9w8b9w7cN08b9w7b908b9w7b9w8b907cNw8b9w8b91HcEx3NJCJAAAAFXRSTlP/3QWSgA+lHPlu6Di4XtIrxk/xRADGudUoAAAB9UlEQVR42tWYwbKjIBREG0GJKkRj/v9bZ1ZvRC99rzib11tTB9qqnKoW3/+X38vy7ifzQ1b/wk/8Q1bCv3y6Z6wFh2x2llIRGB6xRhzz6p+wVhRJD1gRZZYHrADYSyqsjFPGZtYbuFESesUysZXlcMnYyJpxTW5keQh5N7G6CUJCE2uHFNfEGiBmbmB1H4jxDawNcqbuPmtAJTtj6RZ0lpIwiR5jNmgfNtHHwLXPWfFYcS2NMdxkjac/dNaNCJPo3yf9pFuseHbDrBsRFguGs8te8Q4rXzTjVSPCIHp3FePKWbzi30xE+4zlBMmoJaGLfpLUmAmLiN4Xyibahy76WZRQMLJ2WX27on2oFvQVac8yi4p+J2forA0V8W1c++AVS1f1H6p9KKLHxk9RWKmsyB+VLC76gV65DLjokdg5KmsEMXsiDwXWSmTc9ezSoKJHoi9zUVihbMHfQOSsXB7Mrz1S1huKPde69sEsiKgNt8hYTjiWlAyENeu7IFe1D15RSEBN+yCiXw17K1RZm/w7UtJVWYN8f1ZyLlkVb2bT4vIVVrINH1dqX2YttkHmIWsfVWs646wcRFYis6fIVGpfYq1kjpGSW8kSRD+xYSmXRM0Ang9eSZioVdy/5pWaLqzIRyIpuVxYozvGf1m67I7pf/s3UXv+AP61NI2Y+BbSAAAAAElFTkSuQmCC" height=25></a>
&nbsp;
<a href="https://discord.com/invite/yz6tGVsf5p"><img src="https://img.shields.io/badge/-Discord-424549?style=social&logo=discord" height=25></a>
<a href="https://discord.com/invite/yz6tGVsf5p"><img src="https://img.shields.io/badge/-Discord-424549?style=social&logo=discord" height=25></a>
&nbsp;
<a href="https://t.me/affineworkos"><img src="https://img.shields.io/badge/-Telegram-red?style=social&logo=telegram" height=25></a>
<a href="https://t.me/affineworkos"><img src="https://img.shields.io/badge/-Telegram-red?style=social&logo=telegram" height=25></a>
&nbsp;
<a href="https://twitter.com/AffineOfficial"><img src="https://img.shields.io/badge/-Twitter-red?style=social&logo=twitter" height=25></a>
<a href="https://twitter.com/AffineOfficial"><img src="https://img.shields.io/badge/-Twitter-red?style=social&logo=twitter" height=25></a>
&nbsp;
<a href="https://medium.com/@affineworkos"><img src="https://img.shields.io/badge/-Medium-red?style=social&logo=medium" height=25></a>
</p>
@@ -48,100 +50,53 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/21084335/182552060-972cac0e-6258-4ccb-85bd-3bb466c30ccd.png"><p/>
# Stay Up-to-Date and Support Us
# :star: Support Us and Keep Updated :star:
![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif)
# How to use
# Getting Started
<h3 align="center">
🥳🥳🥳 Our web live demo is ready! 🥳🥳🥳
</h3>
[![affine.pro](https://img.shields.io/static/v1?label=Try%20it%20Online&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAhpJREFUWEdjZEACtnl3MxgY/0YzMjAaMzAwcCLLUYH9/T/D/7MM/5mXHp6kPANmHiOI4Zx9Xfg3C+tKBob/zlSwiAgjGPey/vkdvneq5luwA+zy7+yhn+Vwv+89NFHFhREU7IyM/6YT4WyqK/n/nymT0Tb/1mFGBkYbqptOhIH/Gf4fYbTLv/2NBgmOCOvBSr6DHPCfWNW0UEe2A2x1uRlakiXBbtpx6jND+7KXZLmPbAdURokzeJjxwi31rrzH8OX7P5IdQbYDtnUoMXBzMMEt7Fj2imH7qU/0cQBy8MNsPHL5K0P13Of0cQB68MNsJScaSI4CHk4mhq3tSnCf3n36k0FZmh3Mn7L+DcPqgx9ICgWSHeBpxsdQESUGtgRk+eqDH+H8O09/MiR3P6atA1qTJRlsdLnhPgYlPOQQCW96wPDi3R+iHUFSCKAHP8wydEeREg0kOQA9+JOgwR1qL8CQEygC9jWp0UCSA+aVysIT3JqDHxgmr38DtlRCiIVhZZ0CPNhB6QDkEGIA0Q4gZAkuxxFyBNEOQA7ml+/+MIQ1PUAxG1kelAhB6YMYQLQDCPmQUAjhcgxRDiDWcEKOxOYIohyQGyjCEGIvANaPLfhhBiNHA6hmBBXNhABRDgCV/aBQAAFQpYMrn4PUgNTCACiXEMoNRDmAkC8okR8UDhjYRumAN8sHvGMCSkAD2jUDOWDAO6ewbDQQ3XMAy/oxKownQR0AAAAASUVORK5CYII=&message=%E2%86%92&style=for-the-badge)](https://affine.pro) No installation or registration required! Head over to our website and try it out now.
<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
- [Stay Up-to-Date and Support Us](#stay-up-to-date-and-support-us)
- [How to Use](#how-to-use)
- [Table of contents](#table-of-contents)
- [Shape your page](#shape-your-page)
- [Plan your task](#plan-your-task)
- [Sort your knowledge](#sort-your-knowledge)
- [Create your story](#create-your-story)
- [Documentation](#documentation)
- [Getting Started with development](#getting-started-with-development)
- [Roadmap](#roadmap)
- [Releases](#releases)
- [Feature requests](#feature-requests)
- [FAQ](#faq)
- [The Philosophy of AFFiNE](#the-philosophy-of-affine)
- [Community](#community)
- [Contributors](#contributors)
- [Acknowledgments](#acknowledgments)
- [License](#license)
## Shape your page
![546163d6-4c39-4128-ae7f-55d59bc3b76b](https://user-images.githubusercontent.com/79301703/182365611-b0ba3690-21c0-4d9b-bfbc-0bc15da05aeb.gif)
## Plan your task
![41a7b3a4-32f2-4d18-ac6b-57d1e1fda753](https://user-images.githubusercontent.com/79301703/182366553-1f6558a7-f17b-4611-ab95-aea3ec997154.gif)
## Sort your knowledge
![c9e1ff46-cec2-411b-b89d-6727a5e6f6c3](https://user-images.githubusercontent.com/79301703/182366602-08e44d28-a031-4097-9904-52fb9b1e9e17.gif)
Want to deploy it yourself? AFFiNE can run just about anywhere. Check our
[documentation](#documentation)
for more information.
<br /><br />
⚠️ Please note that AFFiNE is still under active development and is not yet ready for production use. ⚠️
## Create your story
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.
There can be more than Notion and Miro. AFFiNE is a next-gen knowledge base that brings planning, sorting and creating all together. Privacy first, open-source, customizable and ready to use, built with web technologies to ensure consistency and accessibility on Mac, Windows and Linux. We want your data always to be yours, without any sacrifice to your accessibility. Your data is always stored local first, with full support for real-time collaboration through peer-to-peer technology. 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.
# Documentation
### Shape your page
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/).
![546163d6-4c39-4128-ae7f-55d59bc3b76b](https://user-images.githubusercontent.com/79301703/182365611-b0ba3690-21c0-4d9b-bfbc-0bc15da05aeb.gif)
## Getting Started with development
### Plan your task
Please view the path Contribute-to-AFFiNE/Software-Contributions/Quick-Start in the documentation.
![41a7b3a4-32f2-4d18-ac6b-57d1e1fda753](https://user-images.githubusercontent.com/79301703/182366553-1f6558a7-f17b-4611-ab95-aea3ec997154.gif)
# Roadmap
### Sort your knowledge
Yes! Permanent storage, collaboration, stable release is planned! Check it [here](https://github.com/toeverything/AFFiNE/issues/293)
![c9e1ff46-cec2-411b-b89d-6727a5e6f6c3](https://user-images.githubusercontent.com/79301703/182366602-08e44d28-a031-4097-9904-52fb9b1e9e17.gif)
# Releases
# Useful Links
Get our latest [release notes](https://github.com/toeverything/AFFiNE/wiki) from here.
- [AFFiNE Documentation](https://docs.affine.pro/affine/) - More detailed documentation on how to use and develop with AFFiNE
# Feature requests
- [Feature Roadmap](https://github.com/toeverything/AFFiNE/issues/293) - Looking for a feature? It might already be planned for release - you can check here
- [Release Notes](https://github.com/toeverything/AFFiNE/wiki) - Find out what changes we are making and how we are improving AFFiNE
Please go to [feature requests](https://github.com/toeverything/AFFiNE/issues).
- [Contributing Guide](/docs/CONTRIBUTING.md) - Want to help improve AFFiNE? You might not even need to write a line of code. Find out how you can contribute.
- [Code of Conduct](/docs/CODE_OF_CONDUCT.md) - How we promote and maintain a harassment-free experience for everyone in our community.
# FAQ
Get quick help on [Telegram](https://t.me/affineworkos) or [Discord](https://discord.gg/yz6tGVsf5p) and join our community of developers and contributors.
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/).
- AFFiNE Communities: [Discord](https://discord.gg/yz6tGVsf5p) | [Telegram](https://t.me/affineworkos) | [Twitter](https://twitter.com/AffineOfficial) |
[Medium](https://medium.com/@affineworkos) | [AFFiNE Blog](https://blog.affine.pro)
# Contact Us
You may contact us by emailing to: contact@toeverything.info
Feel free to send us an email: contact@toeverything.info
# The Philosophy of AFFiNE
@@ -186,12 +141,6 @@ We would also like to give thanks to open-source projects that make affine possi
Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone.
# Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
[Discuss AFFiNE on GitHub](https://github.com/toeverything/AFFiNE/discussions)
# Contributors
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
@@ -250,4 +199,4 @@ For help, discussion about best practices, or any other conversation that would
AFFiNE is distributed under the terms of MIT license.
See LICENSE for details.
See [LICENSE](/LICENSE) for details.
+5 -7
View File
@@ -1,4 +1,4 @@
# Welcome to ourcontributing guide <!-- omit in toc -->
# Welcome to our contributing 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:.
@@ -10,7 +10,7 @@ Use the table of contents icon on the top left corner of this document to get to
## 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:
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)
@@ -19,19 +19,17 @@ To get an overview of the project, read the [README](README.md). Here are some r
## 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:.
Check to see what [types of contributions](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
#### Create a new issue or feature request
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 dont assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
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 our [Labels](https://github.com/toeverything/AFFiNE/labels) for more information. As a general rule, we dont assign issues to anyone. If you find an issue to work on, you are welcome to open a PR with a fix.
### Make Changes
+31
View File
@@ -0,0 +1,31 @@
# Types of contributions :memo:
You can contribute to AFFiNE in several ways. This repo is a place to discuss and collaborate on AFFiNE!
### :mega: Discussions
Discussions are where we have conversations.
If you'd like help troubleshooting a docs PR you're working on, have a great new idea, or want to share something amazing you've learned in our docs, join us in [discussions](https://github.com/toeverything/AFFiNE/discussions).
### :lady_beetle: Issues
[Issues](https://docs.github.com/en/github/managing-your-work-on-github/about-issues) are used to track tasks that contributors can help with. If an issue has a triage label, we haven't reviewed it yet, and you shouldn't begin work on it.
If you've found something in the content or the website that should be updated, search open issues to see if someone else has reported the same thing. If it's something new, open an issue using a [template](https://github.com/toeverything/AFFiNE/issues/new/choose). We'll use the issue to have a conversation about the problem you want to fix.
### :hammer_and_wrench: Pull requests
A [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests) is a way to suggest changes in our repository. When we merge those changes, they should be deployed to the live site within 24 hours. :earth_africa:
You can [create a new pull request](https://github.com/toeverything/AFFiNE/compare) and view [current pull requests](https://github.com/toeverything/AFFiNE/pulls).
### :question: Support
We are a small team working hard to keep up with the documentation demands of a continuously changing product.
You may be able to find additional help and information on our social media platforms and groups - the links to these can be found in our [README](../README.md).
### :earth_asia: Translations
AFFiNE is internationalized and available in multiple languages. The source content in this repository is written in English. We integrate with an external localization platform to work with the community in localizing the English content.
**We do not currently offer this feature**, but we hope to in the future.
+2 -5
View File
@@ -134,17 +134,14 @@ const AffineBoard = ({
}
})
);
let pageBindingsString = (
const pageBindingsString = (
await services.api.editorBlock.get({
workspace: workspace,
ids: [rootBlockId],
})
)?.[0].properties.bindings?.value;
console.log(123123123);
let pageBindings = JSON.parse(pageBindingsString ?? '{}');
console.log(pageBindings, 3333, bindings);
const pageBindings = JSON.parse(pageBindingsString ?? '{}');
Object.keys(bindings).forEach(bindingsKey => {
console.log(345345345345345);
if (!bindings[bindingsKey]) {
delete pageBindings[bindingsKey];
} else {
@@ -35,6 +35,7 @@ const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
export const LineTools = ({ app }: { app: TldrawApp }) => {
const activeTool = app.useStore(activeToolSelector);
const [visible, setVisible] = useState(false);
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
TDShapeType.Line
@@ -51,8 +52,10 @@ export const LineTools = ({ app }: { app: TldrawApp }) => {
return (
<Popover
visible={visible}
placement="right-start"
trigger="click"
onClick={() => setVisible(prev => !prev)}
onClickAway={() => setVisible(false)}
content={
<ShapesContainer>
{shapes.map(({ type, label, tooltip, icon: Icon }) => (
@@ -60,6 +63,7 @@ export const LineTools = ({ app }: { app: TldrawApp }) => {
<IconButton
onClick={() => {
app.selectTool(type);
setVisible(false);
setLastActiveTool(type);
}}
>
@@ -71,6 +71,7 @@ const activeToolSelector = (s: TDSnapshot) => s.appState.activeTool;
export const ShapeTools = ({ app }: { app: TldrawApp }) => {
const activeTool = app.useStore(activeToolSelector);
const [visible, setVisible] = useState(false);
const [lastActiveTool, setLastActiveTool] = useState<ShapeTypes>(
TDShapeType.Rectangle
@@ -87,8 +88,10 @@ export const ShapeTools = ({ app }: { app: TldrawApp }) => {
return (
<Popover
visible={visible}
placement="right-start"
trigger="click"
onClick={() => setVisible(prev => !prev)}
onClickAway={() => setVisible(false)}
content={
<ShapesContainer>
{shapes.map(({ type, label, tooltip, icon: Icon }) => (
@@ -96,6 +99,7 @@ export const ShapeTools = ({ app }: { app: TldrawApp }) => {
<IconButton
onClick={() => {
app.selectTool(type);
setVisible(false);
setLastActiveTool(type);
}}
>
@@ -12,12 +12,12 @@ import {
import {
IconButton,
PopoverContainer,
styled,
// MuiIconButton as IconButton,
// MuiTooltip as Tooltip,
Tooltip,
useTheme,
} from '@toeverything/components/ui';
import style9 from 'style9';
import { TldrawApp } from '@toeverything/components/board-state';
import {
@@ -90,8 +90,8 @@ export const ToolsPanel = ({ app }: { app: TldrawApp }) => {
}}
direction="none"
>
<div className={styles('container')}>
<div className={styles('toolBar')}>
<Container>
<ToolBar>
{tools.map(
({
type,
@@ -127,24 +127,22 @@ export const ToolsPanel = ({ app }: { app: TldrawApp }) => {
</Tooltip>
)
)}
</div>
</div>
</ToolBar>
</Container>
</PopoverContainer>
);
};
const styles = style9.create({
container: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
height: '100%',
},
toolBar: {
display: 'flex',
flexDirection: 'column',
backgroundColor: '#ffffff',
borderRadius: '10px',
padding: '4px 4px',
},
const Container = styled('div')({
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
height: '100%',
});
const ToolBar = styled('div')({
display: 'flex',
flexDirection: 'column',
backgroundColor: '#ffffff',
borderRadius: '10px',
padding: '4px 4px',
});
@@ -12,7 +12,7 @@ import {
styled,
Tooltip,
} from '@toeverything/components/ui';
import { ReactElement, type CSSProperties } from 'react';
import { useState, type CSSProperties, type ReactElement } from 'react';
import { Palette } from '../../palette';
import { Pen } from './Pen';
@@ -106,6 +106,7 @@ const PENCIL_CONFIGS_MAP = PENCIL_CONFIGS.reduce<
export const PenTools = ({ app }: { app: TldrawApp }) => {
const appCurrentTool = app.useStore(state => state.appState.activeTool);
const [visible, setVisible] = useState(false);
const chosenPen =
PENCIL_CONFIGS.find(config => config.key === appCurrentTool) ||
PENCIL_CONFIGS[0];
@@ -134,8 +135,10 @@ export const PenTools = ({ app }: { app: TldrawApp }) => {
return (
<Popover
visible={visible}
placement="right-start"
trigger="click"
onClick={() => setVisible(prev => !prev)}
onClickAway={() => setVisible(false)}
content={
<Container>
<PensContainer>
@@ -153,7 +156,10 @@ export const PenTools = ({ app }: { app: TldrawApp }) => {
name={title}
primaryColor={color_vars['--color-0']}
secondaryColor={color_vars['--color-1']}
onClick={() => setPen(key)}
onClick={() => {
setVisible(false);
setPen(key);
}}
/>
);
}
@@ -163,7 +169,10 @@ export const PenTools = ({ app }: { app: TldrawApp }) => {
<Palette
selected={chosenColor}
colors={PENCIL_CONFIGS_MAP[chosenPenKey].colors}
onSelect={color => setPenColor(color)}
onSelect={color => {
setVisible(false);
setPenColor(color);
}}
/>
<div />
</Container>
+1 -1
View File
@@ -1,5 +1,5 @@
import { ArrowRightIcon } from '@toeverything/components/icons';
import { ReactElement, useRef, useState } from 'react';
import { useRef, useState, type ReactElement } from 'react';
import { Divider } from '../divider';
import {
MuiGrow as Grow,
+9 -2
View File
@@ -35,6 +35,8 @@ export const Popper = ({
offset = [0, 5],
showArrow = false,
popperHandlerRef,
onClick,
onClickAway,
...popperProps
}: PopperProps) => {
const [anchorEl, setAnchorEl] = useState<VirtualElement>(null);
@@ -97,15 +99,20 @@ export const Popper = ({
return (
<ClickAwayListener
onClickAway={() => {
setVisible(false);
if (visibleControlledByParent) {
onClickAway?.();
} else {
setVisible(false);
}
}}
>
<Container>
{isAnchorCustom ? null : (
<div
ref={(dom: HTMLDivElement) => setAnchorEl(dom)}
onClick={() => {
onClick={e => {
if (!hasClickTrigger || visibleControlledByParent) {
onClick?.(e);
return;
}
setVisible(!visible);
@@ -62,4 +62,6 @@ export type PopperProps = {
showArrow?: boolean;
popperHandlerRef?: Ref<PopperHandler>;
onClickAway?: () => void;
} & Omit<PopperUnstyledProps, 'open' | 'ref'>;
+1 -1
View File
@@ -12,7 +12,7 @@
"file-selector": "^0.6.0",
"flexsearch": "^0.7.21",
"lib0": "^0.2.52",
"lru-cache": "^7.13.2",
"lru-cache": "^7.14.0",
"ts-debounce": "^4.0.0"
},
"dependencies": {
+20 -15
View File
@@ -1,3 +1,10 @@
import {
BlockFlavorKeys,
BlockFlavors,
BlockTypeKeys,
BlockTypes,
} from '../types';
import { getLogger } from '../utils';
import {
BlockInstance,
BlockListener,
@@ -6,14 +13,7 @@ import {
ContentTypes,
HistoryManager,
MapOperation,
} from '../adapter';
import {
BlockFlavorKeys,
BlockFlavors,
BlockTypeKeys,
BlockTypes,
} from '../types';
import { getLogger } from '../utils';
} from '../yjs/types';
declare const JWT_DEV: boolean;
const logger = getLogger('BlockDB:block');
@@ -29,10 +29,10 @@ export class AbstractBlock<
private readonly _id: string;
private readonly _block: BlockInstance<C>;
private readonly _history: HistoryManager;
private readonly _root?: AbstractBlock<B, C>;
private readonly _root: AbstractBlock<B, C> | undefined;
private readonly _parentListener: Map<string, BlockListener>;
private _parent?: AbstractBlock<B, C>;
private _parent: AbstractBlock<B, C> | undefined;
private _changeParent?: () => void;
constructor(
@@ -48,7 +48,9 @@ export class AbstractBlock<
this._parentListener = new Map();
JWT_DEV && logger_debug(`init: exists ${this._id}`);
if (parent) this._refreshParent(parent);
if (parent) {
this._refreshParent(parent);
}
}
public get root() {
@@ -146,7 +148,7 @@ export class AbstractBlock<
return new Date(timestamp)
.toISOString()
.split('T')[0]
.replace(/-/g, '');
?.replace(/-/g, '');
}
// eslint-disable-next-line no-empty
} catch (e) {}
@@ -259,7 +261,7 @@ export class AbstractBlock<
}
/**
* Insert sub-Block
* Insert children block
* @param block Block instance
* @param position Insertion position, if it is empty, it will be inserted at the end. If the block already exists, the position will be moved
* @returns
@@ -270,9 +272,12 @@ export class AbstractBlock<
) {
JWT_DEV && logger(`insertChildren: start`);
if (block.id === this._id) return; // avoid self-reference
if (block.id === this._id) {
// avoid self-reference
return;
}
if (
this.type !== BlockTypes.block || // binary cannot insert subblocks
this.type !== BlockTypes.block || // binary cannot insert children blocks
(block.type !== BlockTypes.block &&
this.flavor !== BlockFlavors.workspace) // binary can only be inserted into workspace
) {
+23 -15
View File
@@ -1,3 +1,5 @@
import { BlockItem } from '../types';
import { getLogger } from '../utils';
import {
ArrayOperation,
BlockInstance,
@@ -5,9 +7,7 @@ import {
ContentTypes,
InternalPlainObject,
MapOperation,
} from '../adapter';
import { BlockItem } from '../types';
import { getLogger } from '../utils';
} from '../yjs/types';
import { AbstractBlock } from './abstract';
import { BlockCapability } from './capability';
@@ -23,8 +23,8 @@ export interface Decoration extends InternalPlainObject {
type Validator = <T>(value: T | undefined) => boolean | void;
export type IndexMetadata = Readonly<{
content?: string;
reference?: string;
content: string | undefined;
reference: string | undefined;
tags: string[];
}>;
export type QueryMetadata = Readonly<
@@ -51,7 +51,7 @@ export class BaseBlock<
B extends BlockInstance<C>,
C extends ContentOperation
> extends AbstractBlock<B, C> {
private readonly _exporters?: Exporters;
private readonly _exporters: Exporters | undefined;
private readonly _contentExportersGetter: () => Map<
string,
ReadableContentExporter<string, any>
@@ -68,7 +68,7 @@ export class BaseBlock<
ReadableContentExporter<string[], any>
>;
validators: Map<string, Validator> = new Map();
private readonly _validators: Map<string, Validator> = new Map();
constructor(
block: B,
@@ -157,14 +157,14 @@ export class BaseBlock<
setValidator(key: string, validator?: Validator) {
if (validator) {
this.validators.set(key, validator);
this._validators.set(key, validator);
} else {
this.validators.delete(key);
this._validators.delete(key);
}
}
private validate(key: string, value: unknown): boolean {
const validate = this.validators.get(key);
const validate = this._validators.get(key);
if (validate) {
return validate(value) === false ? false : true;
}
@@ -179,7 +179,7 @@ export class BaseBlock<
}
/**
* Get an instance of the child Block
* Get an instance of the child block
* @param blockId block id
*/
private get_children_instance(blockId?: string): BaseBlock<B, C>[] {
@@ -201,9 +201,15 @@ export class BaseBlock<
}
try {
const parent_page = this._getParentPage(false);
if (parent_page) metadata['page'] = parent_page;
if (this.group) metadata['group'] = this.group.id;
if (this.parent) metadata['parent'] = this.parent.id;
if (parent_page) {
metadata['page'] = parent_page;
}
if (this.group) {
metadata['group'] = this.group.id;
}
if (this.parent) {
metadata['parent'] = this.parent.id;
}
} catch (e) {
logger(`Failed to export default metadata`, e);
}
@@ -228,7 +234,9 @@ export class BaseBlock<
for (const [name, exporter] of this._contentExportersGetter()) {
try {
const content = exporter(this.getContent());
if (content) contents.push(content);
if (content) {
contents.push(content);
}
} catch (err) {
logger(`Failed to export content: ${name}`, err);
}
+23 -10
View File
@@ -7,14 +7,14 @@ import produce from 'immer';
import LRUCache from 'lru-cache';
import sift, { Query } from 'sift';
import { BlockFlavors } from '../types';
import { BlockEventBus, getLogger } from '../utils';
import {
AsyncDatabaseAdapter,
BlockInstance,
ChangedStates,
ContentOperation,
} from '../adapter';
import { BlockFlavors } from '../types';
import { BlockEventBus, getLogger } from '../utils';
} from '../yjs/types';
import { BaseBlock, IndexMetadata, QueryMetadata } from './base';
@@ -315,7 +315,9 @@ export class BlockIndexer<
private _testMetaKey(key: string) {
try {
const metadata = this._blockMetadata.values().next().value;
if (!metadata || typeof metadata !== 'object') return false;
if (!metadata || typeof metadata !== 'object') {
return false;
}
return !!(key in metadata);
} catch (e) {
return false;
@@ -324,8 +326,11 @@ export class BlockIndexer<
private _getSortedMetadata(sort: string, desc?: boolean) {
const sorter = naturalSort(Array.from(this._blockMetadata.entries()));
if (desc) return sorter.desc(([, m]) => m[sort]);
else return sorter.asc(([, m]) => m[sort]);
if (desc) {
return sorter.desc(([, m]) => m[sort]);
} else {
return sorter.asc(([, m]) => m[sort]);
}
}
public query(query: QueryIndexMetadata) {
@@ -337,15 +342,23 @@ export class BlockIndexer<
if ($sort && this._testMetaKey($sort)) {
const metadata = this._getSortedMetadata($sort, $desc);
metadata.forEach(([key, value]) => {
if (matches.length > limit) return;
if (filter(value)) matches.push(key);
if (matches.length > limit) {
return;
}
if (filter(value)) {
matches.push(key);
}
});
return matches;
} else {
this._blockMetadata.forEach((value, key) => {
if (matches.length > limit) return;
if (filter(value)) matches.push(key);
if (matches.length > limit) {
return;
}
if (filter(value)) {
matches.push(key);
}
});
return matches;
+70 -47
View File
@@ -1,26 +1,7 @@
/* eslint-disable max-lines */
import { DocumentSearchOptions } from 'flexsearch';
import LRUCache from 'lru-cache';
import {
AsyncDatabaseAdapter,
BlockInstance,
BlockListener,
ChangedStates,
Connectivity,
ContentOperation,
ContentTypes,
DataExporter,
getDataExporter,
HistoryManager,
YjsAdapter,
YjsContentOperation,
YjsInitOptions,
} from './adapter';
import {
getYjsProviders,
YjsBlockInstance,
YjsProviderOptions,
} from './adapter/yjs';
import {
BaseBlock,
BlockIndexer,
@@ -39,6 +20,26 @@ import {
UUID,
} from './types';
import { BlockEventBus, genUUID, getLogger } from './utils';
import {
getYjsProviders,
YjsAdapter,
YjsBlockInstance,
YjsContentOperation,
YjsInitOptions,
YjsProviderOptions,
} from './yjs';
import {
AsyncDatabaseAdapter,
BlockInstance,
BlockListener,
ChangedStates,
Connectivity,
ContentOperation,
ContentTypes,
DataExporter,
getDataExporter,
HistoryManager,
} from './yjs/types';
declare const JWT_DEV: boolean;
@@ -157,8 +158,11 @@ export class BlockClient<
public addBlockListener(tag: string, listener: BlockListener) {
const bus = this._eventBus.topic<ChangedStates>('updated');
if (tag !== 'index' || !bus.has(tag)) bus.on(tag, listener);
else console.error(`block listener ${tag} is reserved or exists`);
if (tag !== 'index' || !bus.has(tag)) {
bus.on(tag, listener);
} else {
console.error(`block listener ${tag} is reserved or exists`);
}
}
public removeBlockListener(tag: string) {
@@ -170,8 +174,11 @@ export class BlockClient<
listener: BlockListener<Set<string>>
) {
const bus = this._eventBus.topic<ChangedStates<Set<string>>>('editing');
if (tag !== 'index' || !bus.has(tag)) bus.on(tag, listener);
else console.error(`editing listener ${tag} is reserved or exists`);
if (tag !== 'index' || !bus.has(tag)) {
bus.on(tag, listener);
} else {
console.error(`editing listener ${tag} is reserved or exists`);
}
}
public removeEditingListener(tag: string) {
@@ -184,15 +191,18 @@ export class BlockClient<
) {
const bus =
this._eventBus.topic<ChangedStates<Connectivity>>('connectivity');
if (tag !== 'index' || !bus.has(tag)) bus.on(tag, listener);
else
if (tag !== 'index' || !bus.has(tag)) {
bus.on(tag, listener);
} else {
console.error(`connectivity listener ${tag} is reserved or exists`);
}
}
public removeConnectivityListener(tag: string) {
this._eventBus.topic('connectivity').off(tag);
}
// @ts-ignore
private inspector() {
return {
...this._adapter.inspector(),
@@ -220,7 +230,9 @@ export class BlockClient<
this.addBlockListener('index', async states => {
await Promise.allSettled(
Array.from(states.entries()).map(([id, state]) => {
if (state === 'delete') this._blockCaches.delete(id);
if (state === 'delete') {
this._blockCaches.delete(id);
}
return this._blockIndexer.refreshIndex(id, state);
})
);
@@ -255,7 +267,7 @@ export class BlockClient<
}
/**
* research all
* Full text search
* @param part_of_title_or_content Title or content keyword, support Chinese
* @param part_of_title_or_content.index search range, optional values: title, ttl, content, reference
* @param part_of_title_or_content.tag tag, string or array of strings, supports multiple tags
@@ -295,7 +307,9 @@ export class BlockClient<
this.search(part_of_title_or_content).flatMap(({ result }) =>
result.map(async id => {
const page = this._pageMapping.get(id as string);
if (page) return page;
if (page) {
return page;
}
const block = await this.get(id as BlockTypeKeys);
return this.set_page(block);
})
@@ -307,9 +321,10 @@ export class BlockClient<
}
return Promise.all(
this._blockIndexer.getMetadata(pages).map(async page => ({
content: this.get_decoded_content(
await this._adapter.getBlock(page.id)
),
content:
this.get_decoded_content(
await this._adapter.getBlock(page.id)
) || '',
...page,
}))
);
@@ -327,9 +342,10 @@ export class BlockClient<
const ids = this.query(query);
return Promise.all(
this._blockIndexer.getMetadata(ids).map(async page => ({
content: this.get_decoded_content(
await this._adapter.getBlock(page.id)
),
content:
this.get_decoded_content(
await this._adapter.getBlock(page.id)
) || '',
...page,
}))
);
@@ -378,7 +394,7 @@ export class BlockClient<
private async get_parent(id: string) {
const parents = this._parentMapping.get(id);
if (parents) {
if (parents && parents[0]) {
const parent_block_id = parents[0];
if (!this._blockCaches.has(parent_block_id)) {
this._blockCaches.set(
@@ -405,7 +421,9 @@ export class BlockClient<
private set_page(block: BaseBlock<B, C>) {
const page = this._pageMapping.get(block.id);
if (page) return page;
if (page) {
return page;
}
const parent_page = block.parent_page;
if (parent_page) {
this._pageMapping.set(block.id, parent_page);
@@ -472,8 +490,9 @@ export class BlockClient<
matched += 1;
}
}
if (matched === conditions.length)
if (matched === conditions.length) {
exporters.push([name, exporter] as const);
}
}
return exporters;
@@ -487,7 +506,9 @@ export class BlockClient<
);
if (exporter) {
const op = block.content.asMap();
if (op) return exporter[1](op);
if (op) {
return exporter[1](op);
}
}
}
return undefined;
@@ -566,7 +587,9 @@ export class BlockClient<
this.set_page(abstract_block);
abstract_block.on('parent', 'client_hook', state => {
const [parent] = state.keys();
this.set_parent(parent, abstract_block.id);
if (parent) {
this.set_parent(parent, abstract_block.id);
}
this.set_page(abstract_block);
});
this._blockCaches.set(abstract_block.id, abstract_block);
@@ -650,13 +673,6 @@ export type BlockInitOptions = NonNullable<
Parameters<typeof BlockClient.init>[1]
>;
export type {
ArrayOperation,
ChangedStates,
Connectivity,
MapOperation,
TextOperation,
} from './adapter';
export type {
BlockSearchItem,
Decoration as BlockDecoration,
@@ -665,4 +681,11 @@ export type {
export { BlockTypes, BucketBackend as BlockBackend } from './types';
export type { BlockTypeKeys } from './types';
export { isBlock } from './utils';
export type {
ArrayOperation,
ChangedStates,
Connectivity,
MapOperation,
TextOperation,
} from './yjs/types';
export type { QueryIndexMetadata };
+3 -3
View File
@@ -1,4 +1,4 @@
import { ContentOperation } from '../adapter';
import { ContentOperation } from '../yjs/types';
import { RefMetadata } from './metadata';
import { UUID } from './uuid';
// base type of block
@@ -60,8 +60,8 @@ export type BlockItem<C extends ContentOperation> = {
flavor: typeof BlockFlavors[BlockFlavorKeys];
children: string[];
readonly created: number; // creation time, UTC timestamp
readonly updated?: number; // update time, UTC timestamp
readonly creator?: string; // creator id
readonly updated?: number | undefined; // update time, UTC timestamp
readonly creator?: string | undefined; // creator id
content: C; // Essentially what is stored here is either Uint8Array (binary resource) or YDoc (structured resource)
};
+1 -1
View File
@@ -81,7 +81,7 @@ class BlockScopedEventBus<T> extends BlockEventBus {
options?: ListenerOptions
) {
if (options?.debounce) {
const { wait, maxWait } = options.debounce;
const { wait, maxWait = 500 } = options.debounce;
const debounced = debounce(listener, wait, { maxWait });
this.on_listener(this._topic, name, e => {
debounced((e as CustomEvent)?.detail);
+3 -1
View File
@@ -39,7 +39,9 @@ export function getLogger(namespace: string) {
if (JWT_DEV) {
const logger = debug(namespace);
logger.log = console.log.bind(console);
if (JWT_DEV === ('testing' as any)) logger.enabled = true;
if (JWT_DEV === ('testing' as any)) {
logger.enabled = true;
}
return logger;
} else {
return () => {};
@@ -25,7 +25,7 @@ export class YjsRemoteBinaries {
} else {
// TODO: Remote Load
try {
const file = await this._remoteStorage?.instance.getBuffData(
const file = await this._remoteStorage?.instance?.getBuffData(
name
);
console.log(file);
@@ -44,11 +44,11 @@ export class YjsRemoteBinaries {
this._binaries.set(name, binary);
if (this._remoteStorage) {
// TODO: Remote Save, if there is an object with the same name remotely, the upload is skipped, because the file name is the hash of the file content
const has_file = this._remoteStorage.instance.exist(name);
const has_file = this._remoteStorage.instance?.exist(name);
if (!has_file) {
const upload_file = new File(binary.toArray(), name);
await this._remoteStorage.instance
.upload(upload_file)
?.upload(upload_file)
.catch(err => {
throw new Error(`${err} upload error`);
});
@@ -5,8 +5,8 @@ import {
transact,
} from 'yjs';
import { BlockItem, BlockTypes } from '../../types';
import { BlockInstance, BlockListener, HistoryManager } from '../index';
import { BlockItem, BlockTypes } from '../types';
import { BlockInstance, BlockListener, HistoryManager } from './types';
import { YjsHistoryManager } from './history';
import { ChildrenListenerHandler, ContentListenerHandler } from './listener';
@@ -34,7 +34,7 @@ type YjsBlockInstanceProps = {
export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
private readonly _id: string;
private readonly _block: YMap<unknown>;
private readonly _binary?: YArray<ArrayBuffer>;
private readonly _binary: YArray<ArrayBuffer> | undefined;
private readonly _children: YArray<string>;
private readonly _setBlock: (
id: string,
@@ -48,8 +48,7 @@ export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
private readonly _childrenListeners: Map<string, BlockListener>;
private readonly _contentListeners: Map<string, BlockListener>;
// eslint-disable-next-line @typescript-eslint/naming-convention
_childrenMap: Map<string, number>;
private _childrenMap: Map<string, number>;
constructor(props: YjsBlockInstanceProps) {
this._id = props.id;
@@ -184,7 +183,9 @@ export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
}
hasChildren(id: string): boolean {
if (this.children.includes(id)) return true;
if (this.children.includes(id)) {
return true;
}
return this.getChildren().some(block => block.hasChildren(id));
}
@@ -263,7 +264,9 @@ export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
break;
}
}
if (id) failed.push(id);
if (id) {
failed.push(id);
}
}
this._childrenMap = getMapFromYArray(this._children);
@@ -32,7 +32,7 @@ export class GateKeeper {
return creator === this._userId || !!this._common.get(block_id);
}
checkDeleteLists(block_ids: string[]) {
checkDeleteLists(block_ids: string[]): [string[], string[]] {
const success = [];
const fail = [];
for (const block_id of block_ids) {
@@ -1,10 +1,11 @@
import { Map as YMap, UndoManager } from 'yjs';
import { HistoryCallback, HistoryManager } from '../../adapter';
import { HistoryCallback, HistoryManager } from './types';
type StackItem = UndoManager['undoStack'][0];
export class YjsHistoryManager implements HistoryManager {
// @ts-ignore
private readonly _blocks: YMap<any>;
private readonly _historyManager: UndoManager;
private readonly _pushListeners: Map<string, HistoryCallback<any>>;
@@ -56,7 +57,7 @@ export class YjsHistoryManager implements HistoryManager {
}
break(): void {
// this.#history_manager.
// this._historyManager.
}
undo<T = unknown>(): Map<string, T> | undefined {
@@ -18,15 +18,15 @@ import {
transact,
} from 'yjs';
import { BlockItem, BlockTypes } from '../types';
import { getLogger, sha3, sleep } from '../utils';
import {
AsyncDatabaseAdapter,
BlockListener,
ChangedStateKeys,
Connectivity,
HistoryManager,
} from '../../adapter';
import { BlockItem, BlockTypes } from '../../types';
import { getLogger, sha3, sleep } from '../../utils';
} from './types';
import { YjsRemoteBinaries } from './binary';
import { YjsBlockInstance } from './block';
@@ -40,6 +40,7 @@ import {
import { YjsProvider } from './provider';
declare const JWT_DEV: boolean;
// @ts-ignore
const logger = getLogger('BlockDB:yjs');
type ConnectivityListener = (
@@ -54,7 +55,7 @@ type YjsProviders = {
gatekeeper: GateKeeper;
connListener: { listeners?: ConnectivityListener };
userId: string;
remoteToken?: string; // remote storage token
remoteToken: string | undefined; // remote storage token
};
const _yjsDatabaseInstance = new Map<string, YjsProviders>();
@@ -70,8 +71,8 @@ async function _initYjsDatabase(
workspace: string,
options: {
userId: string;
token?: string;
provider?: Record<string, YjsProvider>;
token?: string | undefined;
provider?: Record<string, YjsProvider> | undefined;
}
): Promise<YjsProviders> {
if (_asyncInitLoading.has(workspace)) {
@@ -243,9 +244,10 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
typeof updated === 'number' &&
updated + 1000 * 10 > Date.now()
) {
if (!editing_mapping[editing])
if (!editing_mapping[editing]) {
editing_mapping[editing] = [];
editing_mapping[editing].push(userId);
}
editing_mapping[editing]?.push(userId);
}
}
listener(
@@ -338,7 +340,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
],
});
const [file] = (await fromEvent(handles)) as File[];
const binary = await file.arrayBuffer();
const binary = await file?.arrayBuffer();
// await this._provider.idb.clearData();
const doc = new Doc({ autoLoad: true, shouldLoad: true });
let updated = 0;
@@ -348,7 +350,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
updated += 1;
});
setInterval(() => {
if (updated > 0) updated -= 1;
if (updated > 0) {
updated -= 1;
}
}, 500);
const update_check = new Promise<void>(resolve => {
@@ -362,8 +366,10 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
});
// await new IndexedDBProvider(this._provider.idb.name, doc)
// .whenSynced;
applyUpdate(doc, new Uint8Array(binary));
await update_check;
if (binary) {
applyUpdate(doc, new Uint8Array(binary));
await update_check;
}
console.log('load success');
},
parse: () => this._doc.toJSON(),
@@ -409,8 +415,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
async createBlock(
options: Pick<BlockItem<YjsContentOperation>, 'type' | 'flavor'> & {
uuid?: string;
binary?: ArrayBufferLike;
uuid: string | undefined;
binary: ArrayBufferLike | undefined;
}
): Promise<YjsBlockInstance> {
const uuid = options.uuid || `affine${nanoid(16)}`;
@@ -481,7 +487,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
async getBlock(id: string): Promise<YjsBlockInstance | undefined> {
const block_instance = this.get_block_sync(id);
if (block_instance) return block_instance;
if (block_instance) {
return block_instance;
}
const block = this._blocks.get(id);
if (block && block.get('type') === BlockTypes.binary) {
const binary = await this._binaries.get(
@@ -540,7 +548,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
let uploaded: Promise<void> | undefined;
if (!block.size) {
const content = item.content[INTO_INNER]();
if (!content) return reject();
if (!content) {
return reject();
}
const children = new YArray();
children.push(item.children);
@@ -1,7 +1,8 @@
import { produce } from 'immer';
import { debounce } from 'ts-debounce';
import { YEvent } from 'yjs';
import { BlockListener, ChangedStateKeys } from '../index';
import { BlockListener, ChangedStateKeys } from './types';
let listener_suspend = false;
@@ -6,6 +6,7 @@ import {
Text as YText,
} from 'yjs';
import { ChildrenListenerHandler, ContentListenerHandler } from './listener';
import {
ArrayOperation,
BaseTypes,
@@ -16,21 +17,29 @@ import {
Operable,
TextOperation,
TextToken,
} from '../index';
import { ChildrenListenerHandler, ContentListenerHandler } from './listener';
} from './types';
const INTO_INNER = Symbol('INTO_INNER');
export const DO_NOT_USE_THIS_OR_YOU_WILL_BE_FIRED_SYMBOL_INTO_INNER: typeof INTO_INNER =
INTO_INNER;
function auto_get(root: ContentOperation, key: string): unknown | undefined {
function auto_get(
root: ContentOperation,
key: string | undefined
): unknown | undefined {
const array = root.asArray();
if (array && !Number.isNaN(Number(key))) return array.get(Number(key));
if (array && !Number.isNaN(Number(key))) {
return array.get(Number(key));
}
const map = root.asMap();
if (map) return map.get(key);
if (map && key) {
return map.get(key);
}
const text = root.asText();
if (text) return text.toString();
if (text) {
return text.toString();
}
console.error('auto_get unknown root', root, key);
return undefined;
}
@@ -150,7 +159,9 @@ export class YjsContentOperation implements ContentOperation {
if (root instanceof YjsContentOperation) {
if (path.length === 1) {
const [key] = path;
if (key) return auto_set(root, key, data);
if (key) {
return auto_set(root, key, data);
}
console.error('autoSet unknown path', root, path, data);
return;
}
@@ -191,6 +202,7 @@ export class YjsContentOperation implements ContentOperation {
}
// eslint-disable-next-line @typescript-eslint/naming-convention
// @ts-ignore
private toJSON() {
return this._content.toJSON();
}
@@ -283,7 +295,9 @@ class YjsArrayOperation<T extends ContentTypes>
get(index: number): Operable<T> | undefined {
const content = this._arrayContent.get(index);
if (content) return this.to_operable(content);
if (content) {
return this.to_operable(content);
}
return undefined;
}
@@ -368,7 +382,9 @@ class YjsMapOperation<T extends ContentTypes>
set(key: string, value: Operable<T>): void {
if (value instanceof YjsContentOperation) {
const content = value[INTO_INNER]();
if (content) this._mapContent.set(key, content as unknown as T);
if (content) {
this._mapContent.set(key, content as unknown as T);
}
} else {
this._mapContent.set(key, value as T);
}
@@ -376,7 +392,9 @@ class YjsMapOperation<T extends ContentTypes>
get(key: string): Operable<T> | undefined {
const content = this._mapContent.get(key);
if (content) return this.to_operable(content);
if (content) {
return this.to_operable(content);
}
return undefined;
}
@@ -7,13 +7,13 @@ import {
WebsocketProvider,
} from '@toeverything/datasource/jwt-rpc';
import { Connectivity } from '../../adapter';
import { BucketBackend } from '../../types';
import { BucketBackend } from '../types';
import { Connectivity } from './types';
type YjsDefaultInstances = {
awareness: Awareness;
doc: Doc;
token?: string;
token?: string | undefined;
workspace: string;
emitState: (connectivity: Connectivity) => void;
};
@@ -139,8 +139,8 @@ interface AsyncDatabaseAdapter<C extends ContentOperation> {
reload(): void;
createBlock(
options: Pick<BlockItem<C>, 'type' | 'flavor'> & {
binary?: ArrayBuffer;
uuid?: string;
binary: ArrayBuffer | undefined;
uuid: string | undefined;
}
): Promise<BlockInstance<C>>;
getBlock(id: string): Promise<BlockInstance<C> | undefined>;
@@ -184,8 +184,6 @@ export const getDataExporter = () => {
return { importData, exportData, hasExporter, installExporter };
};
export { YjsAdapter } from './yjs';
export type { YjsContentOperation, YjsInitOptions } from './yjs';
export type {
AsyncDatabaseAdapter,
BlockPosition,
+52 -5
View File
@@ -184,7 +184,7 @@ importers:
yjs: ^13.5.41
dependencies:
authing-js-sdk: 4.23.35
firebase-admin: 11.0.1_@firebase+app-types@0.7.0
firebase-admin: 11.0.1
lib0: 0.2.52
lru-cache: 7.13.2
nanoid: 4.0.0
@@ -566,6 +566,9 @@ importers:
dependencies:
ffc-js-client-side-sdk: 1.1.5
libs/datasource/jwst/pkg:
specifiers: {}
libs/datasource/jwt:
specifiers:
'@types/debug': ^4.1.7
@@ -583,7 +586,7 @@ importers:
idb-keyval: ^6.2.0
immer: ^9.0.15
lib0: ^0.2.52
lru-cache: ^7.13.2
lru-cache: ^7.14.0
nanoid: ^4.0.0
sha3: ^2.1.4
sift: ^16.0.0
@@ -614,7 +617,7 @@ importers:
file-selector: 0.6.0
flexsearch: 0.7.21
lib0: 0.2.52
lru-cache: 7.13.2
lru-cache: 7.14.0
ts-debounce: 4.0.0
libs/datasource/jwt-rpc:
@@ -3180,6 +3183,15 @@ packages:
- utf-8-validate
dev: true
/@firebase/auth-interop-types/0.1.6_@firebase+util@1.6.3:
resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==}
peerDependencies:
'@firebase/app-types': 0.x
'@firebase/util': 1.x
dependencies:
'@firebase/util': 1.6.3
dev: false
/@firebase/auth-interop-types/0.1.6_pbfwexsq7uf6mrzcwnikj3g37m:
resolution: {integrity: sha512-etIi92fW3CctsmR9e3sYM3Uqnoq861M0Id9mdOPF6PWIg38BXL5k4upCNBggGUpLIS0H1grMOvy/wn1xymwe2g==}
peerDependencies:
@@ -3188,6 +3200,7 @@ packages:
dependencies:
'@firebase/app-types': 0.7.0
'@firebase/util': 1.6.3
dev: true
/@firebase/auth-types/0.11.0_pbfwexsq7uf6mrzcwnikj3g37m:
resolution: {integrity: sha512-q7Bt6cx+ySj9elQHTsKulwk3+qDezhzRBFC9zlQ1BjgMueUOnGMcvqmU0zuKlQ4RhLSH7MNAdBV2znVaoN3Vxw==}
@@ -3223,6 +3236,19 @@ packages:
'@firebase/util': 1.6.3
tslib: 2.4.0
/@firebase/database-compat/0.2.4:
resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==}
dependencies:
'@firebase/component': 0.5.17
'@firebase/database': 0.13.4
'@firebase/database-types': 0.9.12
'@firebase/logger': 0.3.3
'@firebase/util': 1.6.3
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: false
/@firebase/database-compat/0.2.4_@firebase+app-types@0.7.0:
resolution: {integrity: sha512-VtsGixO5mTjNMJn6PwxAJEAR70fj+3blCXIdQKel3q+eYGZAfdqxox1+tzZDnf9NWBJpaOgAHPk3JVDxEo9NFQ==}
dependencies:
@@ -3234,6 +3260,7 @@ packages:
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: true
/@firebase/database-types/0.9.10:
resolution: {integrity: sha512-2ji6nXRRsY+7hgU6zRhUtK0RmSjVWM71taI7Flgaw+BnopCo/lDF5HSwxp8z7LtiHlvQqeRA3Ozqx5VhlAbiKg==}
@@ -3248,6 +3275,19 @@ packages:
'@firebase/app-types': 0.7.0
'@firebase/util': 1.6.3
/@firebase/database/0.13.4:
resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==}
dependencies:
'@firebase/auth-interop-types': 0.1.6_@firebase+util@1.6.3
'@firebase/component': 0.5.17
'@firebase/logger': 0.3.3
'@firebase/util': 1.6.3
faye-websocket: 0.11.4
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: false
/@firebase/database/0.13.4_@firebase+app-types@0.7.0:
resolution: {integrity: sha512-NW7bOoiaC4sJCj6DY/m9xHoFNa0CK32YPMCh6FiMweLCDQbOZM8Ql/Kn6yyuxCb7K7ypz9eSbRlCWQJsJRQjhg==}
dependencies:
@@ -3259,6 +3299,7 @@ packages:
tslib: 2.4.0
transitivePeerDependencies:
- '@firebase/app-types'
dev: true
/@firebase/firestore-compat/0.1.23_53yvy43rwpg2c45kgeszsxtrca:
resolution: {integrity: sha512-QfcuyMAavp//fQnjSfCEpnbWi7spIdKaXys1kOLu7395fLr+U6ykmto1HUMCSz8Yus9cEr/03Ujdi2SUl2GUAA==}
@@ -10332,12 +10373,12 @@ packages:
semver-regex: 2.0.0
dev: true
/firebase-admin/11.0.1_@firebase+app-types@0.7.0:
/firebase-admin/11.0.1:
resolution: {integrity: sha512-rL3wlZbi2Kb/KJgcmj1YHlD4ZhfmhfgRO2YJialxAllm0tj1IQea878hHuBLGmv4DpbW9t9nLvX9kddNR2Y65Q==}
engines: {node: '>=14'}
dependencies:
'@fastify/busboy': 1.1.0
'@firebase/database-compat': 0.2.4_@firebase+app-types@0.7.0
'@firebase/database-compat': 0.2.4
'@firebase/database-types': 0.9.10
'@types/node': 18.0.1
jsonwebtoken: 8.5.1
@@ -13573,6 +13614,12 @@ packages:
/lru-cache/7.13.2:
resolution: {integrity: sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA==}
engines: {node: '>=12'}
dev: false
/lru-cache/7.14.0:
resolution: {integrity: sha512-EIRtP1GrSJny0dqb50QXRUNBxHJhcpxHC++M5tD7RYbvLLn5KVWKsbyswSSqDuU15UFi3bgTQIY8nhDMeF6aDQ==}
engines: {node: '>=12'}
dev: true
/lru-memoizer/2.1.4:
resolution: {integrity: sha512-IXAq50s4qwrOBrXJklY+KhgZF+5y98PDaNo0gi/v2KQBFLyWr+JyFvijZXkGKjQj/h9c0OwoE+JZbwUXce76hQ==}