From 4522e0a56c495f237eea152605dab4342734c6e8 Mon Sep 17 00:00:00 2001 From: SaikaSakura Date: Tue, 9 Aug 2022 18:44:14 +0800 Subject: [PATCH 01/33] feat: new grid drop logic --- .../src/editor/commands/block-commands.ts | 4 +- .../src/editor/drag-drop/drag-drop.ts | 54 ++++++++++++++----- .../src/menu/command-menu/Menu.tsx | 1 + .../src/menu/left-menu/LeftMenuPlugin.tsx | 15 +++--- 4 files changed, 52 insertions(+), 22 deletions(-) diff --git a/libs/components/editor-core/src/editor/commands/block-commands.ts b/libs/components/editor-core/src/editor/commands/block-commands.ts index 54f9e5d2f..5972e9d41 100644 --- a/libs/components/editor-core/src/editor/commands/block-commands.ts +++ b/libs/components/editor-core/src/editor/commands/block-commands.ts @@ -162,7 +162,7 @@ export class BlockCommands { public async moveInNewGridItem( blockId: string, gridItemId: string, - isBefore = false + type = GridDropType.left ) { const block = await this._editor.getBlockById(blockId); if (block) { @@ -175,7 +175,7 @@ export class BlockCommands { await block.remove(); await gridItemBlock.append(block); if (targetGridItemBlock && gridItemBlock) { - if (isBefore) { + if (type === GridDropType.left) { await targetGridItemBlock.before(gridItemBlock); } else { await targetGridItemBlock.after(gridItemBlock); diff --git a/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts b/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts index eccdfb081..be64c07b9 100644 --- a/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts +++ b/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts @@ -95,6 +95,9 @@ export class DragDropManager { } private async _handleDropBlock(event: React.DragEvent) { + const targetBlock = await this._editor.getBlockById( + this._blockDragTargetId + ); if (this._blockDragDirection !== BlockDropPlacement.none) { const blockId = event.dataTransfer.getData(this._blockIdKey); if (!(await this._canBeDrop(event))) return; @@ -109,13 +112,24 @@ export class DragDropManager { this._blockDragDirection ) ) { - await this._editor.commands.blockCommands.createLayoutBlock( - blockId, - this._blockDragTargetId, + const dropType = this._blockDragDirection === BlockDropPlacement.left ? GridDropType.left - : GridDropType.right - ); + : GridDropType.right; + // if target is a grid item create grid item + if (targetBlock.type !== Protocol.Block.Type.gridItem) { + await this._editor.commands.blockCommands.createLayoutBlock( + blockId, + this._blockDragTargetId, + dropType + ); + } else { + await this._editor.commands.blockCommands.moveInNewGridItem( + blockId, + this._blockDragTargetId, + dropType + ); + } } if ( [ @@ -123,9 +137,6 @@ export class DragDropManager { BlockDropPlacement.outerRight, ].includes(this._blockDragDirection) ) { - const targetBlock = await this._editor.getBlockById( - this._blockDragTargetId - ); if (targetBlock.type !== Protocol.Block.Type.grid) { await this._editor.commands.blockCommands.createLayoutBlock( blockId, @@ -154,7 +165,7 @@ export class DragDropManager { await this._editor.commands.blockCommands.moveInNewGridItem( blockId, gridItems[0].id, - true + GridDropType.right ); } } @@ -347,10 +358,10 @@ export class DragDropManager { blockId: string ) { const { clientX, clientY } = event; - this._setBlockDragTargetId(blockId); const path = await this._editor.getBlockPath(blockId); const mousePoint = new Point(clientX, clientY); const rect = domToRect(blockDom); + let targetBlock: AsyncBlock = path[path.length - 1]; /** * IMP: compute the level of the target block * future feature drag drop has level support do not delete @@ -386,13 +397,30 @@ export class DragDropManager { const gridBlocks = path.filter( block => block.type === Protocol.Block.Type.grid ); - // limit grid block floor counts, when drag block to init grid - if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) { + const parentBlock = path[path.length - 2]; + // a new grid should not be grid item`s child + if ( + parentBlock && + parentBlock.type === Protocol.Block.Type.gridItem + ) { + targetBlock = parentBlock; + // gridItem`s parent must be grid block + const gridItemCounts = (await path[path.length - 3].children()) + .length; + if ( + gridItemCounts >= + this._editor.configManager.grid.maxGridItemCount + ) { + direction = BlockDropPlacement.none; + } + // limit grid block floor counts, when drag block to init grid + } else if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) { direction = BlockDropPlacement.none; } } + this._setBlockDragTargetId(targetBlock.id); this._setBlockDragDirection(direction); - return direction; + return { direction, block: targetBlock }; } public handlerEditorDrop(event: React.DragEvent) { diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index b6d174e51..d99d53d9b 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -18,6 +18,7 @@ import { menuItemsMap, } from './config'; import { QueryResult } from '../../search'; +import { getBlockIdByNode } from '@toeverything/utils'; export type CommandMenuProps = { editor: Virgo; diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx index 91402bbd9..5281c21b0 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx @@ -105,16 +105,17 @@ export class LeftMenuPlugin extends BasePlugin { new Point(event.clientX, event.clientY) ); if (block == null || ignoreBlockTypes.includes(block.type)) return; - const direction = await this.editor.dragDropManager.checkBlockDragTypes( - event, - block.dom, - block.id - ); + const { direction, block: targetBlock } = + await this.editor.dragDropManager.checkBlockDragTypes( + event, + block.dom, + block.id + ); this._lineInfo.next({ direction, blockInfo: { - block, - rect: block.dom.getBoundingClientRect(), + block: targetBlock, + rect: targetBlock.dom.getBoundingClientRect(), }, }); }; From 14d7085ec3d7a029bfc69720f45361816b71a14c Mon Sep 17 00:00:00 2001 From: DarkSky <25152247+darkskygit@users.noreply.github.com> Date: Wed, 10 Aug 2022 01:32:32 +0800 Subject: [PATCH 02/33] Update Dockerfile-affine --- .github/deployment/Dockerfile-affine | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/deployment/Dockerfile-affine b/.github/deployment/Dockerfile-affine index 46a46233f..5cf4243ea 100644 --- a/.github/deployment/Dockerfile-affine +++ b/.github/deployment/Dockerfile-affine @@ -2,7 +2,7 @@ FROM node:16-alpine as builder WORKDIR /app COPY . . RUN apk add g++ make python3 git libpng-dev -RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:local +RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:local --skip-nx-cache FROM node:16-alpine as relocate WORKDIR /app @@ -18,4 +18,4 @@ WORKDIR /app COPY --from=relocate /app . EXPOSE 3000 -CMD ["caddy", "run"] \ No newline at end of file +CMD ["caddy", "run"] From 32be658e964edd0f6d9516dc9dca592c8e2e02fb Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 11:15:22 +0800 Subject: [PATCH 03/33] fix:lint --- libs/components/editor-plugins/src/menu/command-menu/Menu.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index b6d174e51..c81b91879 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -189,7 +189,6 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { }, [] ); - useEffect(() => { const sub = hooks .get(HookType.ON_ROOT_NODE_KEYUP) From fa458f06a4b341e213779494956c3f0e5df98190 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 11:42:09 +0800 Subject: [PATCH 04/33] fix:commend-menu postion --- .../editor-plugins/src/menu/command-menu/Menu.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index c81b91879..56c3790a6 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -129,8 +129,12 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { if (clientHeight - rectTop <= COMMAND_MENU_HEIGHT) { setCommandMenuPosition({ left: rect.left - left, - bottom: rectTop - top + 10, - top: 'initial', + top: + rectTop - + top - + COMMAND_MENU_HEIGHT + + 20, + bottom: 'initial', }); } else { setCommandMenuPosition({ @@ -189,6 +193,7 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { }, [] ); + useEffect(() => { const sub = hooks .get(HookType.ON_ROOT_NODE_KEYUP) From b5ae4e9a64791f92d57014412835209d00341b14 Mon Sep 17 00:00:00 2001 From: SaikaSakura Date: Wed, 10 Aug 2022 15:34:26 +0800 Subject: [PATCH 05/33] feat: fix review --- libs/components/editor-plugins/src/menu/command-menu/Menu.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index d99d53d9b..62fd1436a 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -18,8 +18,6 @@ import { menuItemsMap, } from './config'; import { QueryResult } from '../../search'; -import { getBlockIdByNode } from '@toeverything/utils'; - export type CommandMenuProps = { editor: Virgo; hooks: PluginHooks; From 1a9a5d99fa4d1f4285db1086fd2ee7c7f52ee6e2 Mon Sep 17 00:00:00 2001 From: mitsuha Date: Wed, 10 Aug 2022 16:36:24 +0800 Subject: [PATCH 06/33] opti: 1.right panel trigger button cannot click#176; --- .../workspace/docs/components/tabs/Tabs.tsx | 8 +++- .../header/EditorBoardSwitcher/StatusIcon.tsx | 10 +++-- .../header/EditorBoardSwitcher/StatusText.tsx | 37 ++++++++++++------- .../EditorBoardSwitcher/StatusTrack.tsx | 12 ++++-- .../header/EditorBoardSwitcher/Switcher.tsx | 2 + .../layout/src/header/LayoutHeader.tsx | 30 ++++++++------- 6 files changed, 64 insertions(+), 35 deletions(-) diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx index b4a2eaf02..2d56e7edc 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx @@ -10,7 +10,6 @@ const StyledTabs = styled('div')(({ theme }) => { display: 'flex', fontSize: '12px', fontWeight: '600', - color: theme.affine.palette.primary, }; }); @@ -26,13 +25,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; diff --git a/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx index a30e17f91..0c38b3f4e 100644 --- a/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx +++ b/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx @@ -17,16 +17,20 @@ export const StatusIcon = ({ mode }: StatusIconProps) => { const IconWrapper = styled('div')>( ({ theme, mode }) => { return { - width: '20px', - height: '20px', + width: '24px', + height: '24px', borderRadius: '5px', boxShadow: theme.affine.shadows.shadow1, color: theme.affine.palette.primary, cursor: 'pointer', backgroundColor: theme.affine.palette.white, - transform: `translateX(${mode === DocMode.doc ? 0 : 20}px)`, + transform: `translateX(${mode === DocMode.doc ? 0 : 30}px)`, transition: 'transform 300ms ease', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + '& > svg': { fontSize: '20px', }, diff --git a/libs/components/layout/src/header/EditorBoardSwitcher/StatusText.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/StatusText.tsx index 73976d3da..b50e48091 100644 --- a/libs/components/layout/src/header/EditorBoardSwitcher/StatusText.tsx +++ b/libs/components/layout/src/header/EditorBoardSwitcher/StatusText.tsx @@ -2,26 +2,37 @@ import { styled } from '@toeverything/components/ui'; type StatusTextProps = { children: string; + width?: string; active?: boolean; onClick?: () => void; }; -export const StatusText = ({ children, active, onClick }: StatusTextProps) => { +export const StatusText = ({ + children, + width, + active, + onClick, +}: StatusTextProps) => { return ( - + {children} ); }; -const StyledText = styled('div')(({ theme, active }) => { - return { - display: 'inline-flex', - alignItems: 'center', - color: theme.affine.palette.primary, - fontWeight: active ? '500' : '300', - fontSize: '15px', - cursor: 'pointer', - padding: '0 6px', - }; -}); +const StyledText = styled('div')( + ({ theme, width, active }) => { + return { + display: 'inline-flex', + alignItems: 'center', + color: active + ? theme.affine.palette.primary + : 'rgba(62, 111, 219, 0.6)', + fontWeight: active ? '600' : '400', + fontSize: '16px', + lineHeight: '22px', + cursor: 'pointer', + ...(!!width && { width }), + }; + } +); diff --git a/libs/components/layout/src/header/EditorBoardSwitcher/StatusTrack.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/StatusTrack.tsx index bcc3e80fa..f14bacd35 100644 --- a/libs/components/layout/src/header/EditorBoardSwitcher/StatusTrack.tsx +++ b/libs/components/layout/src/header/EditorBoardSwitcher/StatusTrack.tsx @@ -18,11 +18,15 @@ export const StatusTrack: FC = ({ mode, onClick }) => { const Container = styled('div')(({ theme }) => { return { + width: '64px', + height: '32px', backgroundColor: theme.affine.palette.textHover, - borderRadius: '5px', - height: '30px', - width: '50px', + border: '1px solid #ECF1FB', + borderRadius: '8px', cursor: 'pointer', - padding: '5px', + margin: '0 8px', + display: 'flex', + alignItems: 'center', + padding: '0 4px', }; }); diff --git a/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx index 0bfe20971..3c350c237 100644 --- a/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx +++ b/libs/components/layout/src/header/EditorBoardSwitcher/Switcher.tsx @@ -32,6 +32,7 @@ export const Switcher = () => { return ( switchToPageView(DocMode.doc)} > @@ -48,6 +49,7 @@ export const Switcher = () => { }} /> switchToPageView(DocMode.board)} > diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index ccf649764..0d9560666 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -1,4 +1,4 @@ -import { IconButton, styled } from '@toeverything/components/ui'; +import { IconButton, styled, MuiButton } from '@toeverything/components/ui'; import { LogoIcon, SideBarViewIcon, @@ -24,9 +24,13 @@ export const LayoutHeader = () => { - Share + Share
- +
@@ -119,17 +123,19 @@ const StyledHelper = styled('div')({ alignItems: 'center', }); -const StyledShare = styled('div')({ +const StyledShare = styled(MuiButton)<{ disabled?: boolean }>({ padding: '10px 12px', fontWeight: 600, fontSize: '14px', - color: '#3E6FDB', cursor: 'pointer', - - '&:hover': { - background: '#F5F7F8', - borderRadius: '5px', - }, + color: '#98ACBD', + textTransform: 'none', + /* disabled for current time */ + // color: '#3E6FDB', + // '&:hover': { + // background: '#F5F7F8', + // borderRadius: '5px', + // }, }); const StyledLogoIcon = styled(LogoIcon)(({ theme }) => { @@ -141,9 +147,7 @@ const StyledLogoIcon = styled(LogoIcon)(({ theme }) => { const StyledContainerForEditorBoardSwitcher = styled('div')(({ theme }) => { return { - width: '100%', position: 'absolute', - display: 'flex', - justifyContent: 'center', + left: '50%', }; }); From 62bc9876972184b0d7d32a45ac596a49506a1d37 Mon Sep 17 00:00:00 2001 From: CJSS Date: Wed, 10 Aug 2022 16:51:18 +0800 Subject: [PATCH 07/33] docs: added CONTRIBUTING and CODE_OF_CONDUCT files (#175) * Create CODE_OF_CONDUCT.md * Create CONTRIBUTING.md * chore: format Co-authored-by: Whitewater --- CODE_OF_CONDUCT.md | 45 ++++++++++++++++++++++++ CONTRIBUTING.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..de5d75c15 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -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 + +For answers to common questions about this code of conduct, see diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..cc63bee96 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,88 @@ +# Welcome to ourcontributing guide + +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://affine.gitbook.io/affine/) From 56cdcf8622647ba72c37c543455cc5ca49fe92a7 Mon Sep 17 00:00:00 2001 From: austaras Date: Wed, 10 Aug 2022 16:37:31 +0800 Subject: [PATCH 08/33] fix(group): delete group when there's no children --- libs/components/editor-blocks/src/blocks/grid/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/components/editor-blocks/src/blocks/grid/index.ts b/libs/components/editor-blocks/src/blocks/grid/index.ts index 808f54f0a..582014376 100644 --- a/libs/components/editor-blocks/src/blocks/grid/index.ts +++ b/libs/components/editor-blocks/src/blocks/grid/index.ts @@ -36,6 +36,9 @@ export class GridBlock extends BaseView { } return block.remove(); } + if (block.childrenIds.length === 0) { + return block.remove(); + } return true; } } From ffdf247517ff611eb16d05dcaa7e61cb1498672c Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 10 Aug 2022 17:44:30 +0800 Subject: [PATCH 09/33] feat: parent refresh event --- .../jwt/src/adapter/yjs/listener.ts | 6 +- libs/datasource/jwt/src/block/abstract.ts | 83 ++++++++++--------- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/libs/datasource/jwt/src/adapter/yjs/listener.ts b/libs/datasource/jwt/src/adapter/yjs/listener.ts index d9b4de123..36c203bbc 100644 --- a/libs/datasource/jwt/src/adapter/yjs/listener.ts +++ b/libs/datasource/jwt/src/adapter/yjs/listener.ts @@ -67,8 +67,12 @@ export function ChildrenListenerHandler( const keys = Array.from(event.keys.entries()).map( ([key, { action }]) => [key, action] as [string, ChangedStateKeys] ); + const deleted = Array.from(event.changes.deleted.values()) + .flatMap(val => val.content.getContent() as string[]) + .filter(v => v) + .map(k => [k, 'delete'] as [string, ChangedStateKeys]); for (const listener of listeners.values()) { - EmitEvents(keys, listener); + EmitEvents([...keys, ...deleted], listener); } } } diff --git a/libs/datasource/jwt/src/block/abstract.ts b/libs/datasource/jwt/src/block/abstract.ts index 0225aa089..468fd5332 100644 --- a/libs/datasource/jwt/src/block/abstract.ts +++ b/libs/datasource/jwt/src/block/abstract.ts @@ -27,12 +27,13 @@ export class AbstractBlock< C extends ContentOperation > { private readonly _id: string; - readonly #block: BlockInstance; + private readonly _block: BlockInstance; private readonly _history: HistoryManager; private readonly _root?: AbstractBlock; private readonly _parentListener: Map; - _parent?: AbstractBlock; + private _parent?: AbstractBlock; + private _changeParent?: () => void; constructor( block: B, @@ -40,20 +41,14 @@ export class AbstractBlock< parent?: AbstractBlock ) { this._id = block.id; - this.#block = block; - this._history = this.#block.scopedHistory([this._id]); + this._block = block; + this._history = this._block.scopedHistory([this._id]); this._root = root; this._parentListener = new Map(); - this._parent = parent; + JWT_DEV && logger_debug(`init: exists ${this._id}`); - if (parent) { - parent.addChildrenListener(this._id, states => { - if (states.get(this._id) === 'delete') { - this._emitParent(parent._id, 'delete'); - } - }); - } + if (parent) this._refreshParent(parent); } public get root() { @@ -66,7 +61,7 @@ export class AbstractBlock< protected _getParentPage(warning = true): string | undefined { if (this.flavor === 'page') { - return this.#block.id; + return this._block.id; } else if (!this._parent) { if (warning && this.flavor !== 'workspace') { console.warn('parent not found'); @@ -89,7 +84,7 @@ export class AbstractBlock< if (event === 'parent') { this._parentListener.set(name, callback); } else { - this.#block.on(event, name, callback); + this._block.on(event, name, callback); } } @@ -97,42 +92,40 @@ export class AbstractBlock< if (event === 'parent') { this._parentListener.delete(name); } else { - this.#block.off(event, name); + this._block.off(event, name); } } public addChildrenListener(name: string, listener: BlockListener) { - this.#block.addChildrenListener(name, listener); + this._block.addChildrenListener(name, listener); } public removeChildrenListener(name: string) { - this.#block.removeChildrenListener(name); + this._block.removeChildrenListener(name); } public addContentListener(name: string, listener: BlockListener) { - this.#block.addContentListener(name, listener); + this._block.addContentListener(name, listener); } public removeContentListener(name: string) { - this.#block.removeContentListener(name); + this._block.removeContentListener(name); } public getContent< T extends ContentTypes = ContentOperation >(): MapOperation { - if (this.#block.type === BlockTypes.block) { - return this.#block.content.asMap() as MapOperation; + if (this._block.type === BlockTypes.block) { + return this._block.content.asMap() as MapOperation; } throw new Error( - `this block not a structured block: ${this._id}, ${ - this.#block.type - }` + `this block not a structured block: ${this._id}, ${this._block.type}` ); } public getBinary(): ArrayBuffer | undefined { - if (this.#block.type === BlockTypes.binary) { - return this.#block.content.asArray()?.get(0); + if (this._block.type === BlockTypes.binary) { + return this._block.content.asArray()?.get(0); } throw new Error('this block not a binary block'); } @@ -162,7 +155,7 @@ export class AbstractBlock< // Last update UTC time public get lastUpdated(): number { - return this.#block.updated || this.#block.created; + return this._block.updated || this._block.created; } private get last_updated_date(): string | undefined { @@ -171,7 +164,7 @@ export class AbstractBlock< // create UTC time public get created(): number { - return this.#block.created; + return this._block.created; } private get created_date(): string | undefined { @@ -180,11 +173,11 @@ export class AbstractBlock< // creator id public get creator(): string | undefined { - return this.#block.creator; + return this._block.creator; } [_GET_BLOCK]() { - return this.#block; + return this._block; } private _emitParent( @@ -199,8 +192,20 @@ export class AbstractBlock< } } - [_SET_PARENT](parent: AbstractBlock) { + private _refreshParent(parent: AbstractBlock) { + this._changeParent?.(); + parent.addChildrenListener(this._id, states => { + if (states.get(this._id) === 'delete') { + this._emitParent(parent._id, 'delete'); + } + }); + this._parent = parent; + this._changeParent = () => parent.removeChildrenListener(this._id); + } + + [_SET_PARENT](parent: AbstractBlock) { + this._refreshParent(parent); this._emitParent(parent.id); } @@ -234,23 +239,23 @@ export class AbstractBlock< * current block type */ public get type(): typeof BlockTypes[BlockTypeKeys] { - return this.#block.type; + return this._block.type; } /** * current block flavor */ public get flavor(): typeof BlockFlavors[BlockFlavorKeys] { - return this.#block.flavor; + return this._block.flavor; } // TODO: flavor needs optimization setFlavor(flavor: typeof BlockFlavors[BlockFlavorKeys]) { - this.#block.setFlavor(flavor); + this._block.setFlavor(flavor); } public get children(): string[] { - return this.#block.children; + return this._block.children; } /** @@ -274,12 +279,12 @@ export class AbstractBlock< throw new Error('insertChildren: binary not allow insert children'); } - this.#block.insertChildren(block[_GET_BLOCK](), position); + this._block.insertChildren(block[_GET_BLOCK](), position); block[_SET_PARENT](this); } public hasChildren(id: string): boolean { - return this.#block.hasChildren(id); + return this._block.hasChildren(id); } /** @@ -289,11 +294,11 @@ export class AbstractBlock< */ protected get_children(blockId?: string): BlockInstance[] { JWT_DEV && logger(`get children: ${blockId}`); - return this.#block.getChildren([blockId]); + return this._block.getChildren([blockId]); } public removeChildren(blockId?: string) { - this.#block.removeChildren([blockId]); + this._block.removeChildren([blockId]); } public remove() { From 00f5f239b294e71e8732faf01198185de554903b Mon Sep 17 00:00:00 2001 From: austaras Date: Wed, 10 Aug 2022 15:45:58 +0800 Subject: [PATCH 10/33] fix(plugin): hide left menu when block change --- .../editor-plugins/src/menu/group-menu/GropuMenu.tsx | 6 ++++++ .../src/menu/left-menu/LeftMenuDraggable.tsx | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx b/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx index 790ee9863..781c2c3a2 100644 --- a/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx +++ b/libs/components/editor-plugins/src/menu/group-menu/GropuMenu.tsx @@ -168,6 +168,12 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) { useEffect(() => { setShowMenu(false); + + if (groupBlock) { + const unobserve = groupBlock.onUpdate(() => setGroupBlock(null)); + return unobserve; + } + return undefined; }, [groupBlock]); return ( diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx index 996857c2a..4f10f16fb 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx @@ -184,6 +184,14 @@ export const LeftMenuDraggable: FC = props => { return () => sub.unsubscribe(); }, [blockInfo, editor]); + useEffect(() => { + if (block?.block != null) { + const unobserve = block.block.onUpdate(() => setBlock(undefined)); + return unobserve; + } + return undefined; + }, [block?.block]); + useEffect(() => { const sub = lineInfo.subscribe(data => { if (data == null) { From 46f903583b2f0fa28879890123781055e1b893c2 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 18:25:44 +0800 Subject: [PATCH 11/33] fix: style ui --- .../editor-blocks/src/blocks/group/GroupView.tsx | 1 + .../editor-blocks/src/blocks/text/TextView.tsx | 1 + .../editor-blocks/src/blocks/todo/TodoView.tsx | 1 + .../src/components/text-manage/TextManage.tsx | 3 +++ libs/components/ui/src/theme/theme.ts | 9 +++++++++ 5 files changed, 15 insertions(+) diff --git a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx index 193a97f07..9b0f0f910 100644 --- a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx +++ b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx @@ -38,6 +38,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', diff --git a/libs/components/editor-blocks/src/blocks/text/TextView.tsx b/libs/components/editor-blocks/src/blocks/text/TextView.tsx index cb408cb18..2f26e17c3 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextView.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextView.tsx @@ -46,6 +46,7 @@ const TextBlock = styled(TextManage)<{ type: string }>(({ theme, type }) => { return { fontSize: textStyleMap.text.fontSize, lineHeight: textStyleMap.text.lineHeight, + fontWeight: textStyleMap.text.fontWeight, }; } }); diff --git a/libs/components/editor-blocks/src/blocks/todo/TodoView.tsx b/libs/components/editor-blocks/src/blocks/todo/TodoView.tsx index b45c07a79..e5588d46f 100644 --- a/libs/components/editor-blocks/src/blocks/todo/TodoView.tsx +++ b/libs/components/editor-blocks/src/blocks/todo/TodoView.tsx @@ -150,6 +150,7 @@ const TodoBlock = styled('div')({ display: 'flex', '.checkBoxContainer': { marginRight: '4px', + padding: '0 4px', height: '22px', }, '.textContainer': { diff --git a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx index 2e146c770..7928375a3 100644 --- a/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx +++ b/libs/components/editor-blocks/src/components/text-manage/TextManage.tsx @@ -39,6 +39,9 @@ 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', })); const findSlice = (arr: string[], p: string, q: string) => { diff --git a/libs/components/ui/src/theme/theme.ts b/libs/components/ui/src/theme/theme.ts index 70aace812..b8055da05 100644 --- a/libs/components/ui/src/theme/theme.ts +++ b/libs/components/ui/src/theme/theme.ts @@ -173,26 +173,34 @@ export const Theme = { body1: { fontSize: '16px', lineHeight: '22px', + fontWeight: 400, + fontFamily: 'PingFang SC', + color: '#3A4C5C', }, h1: { fontSize: '28px', lineHeight: '40px', + fontWeight: 600, }, h2: { fontSize: '24px', lineHeight: '34px', + fontWeight: 600, }, h3: { fontSize: '20px', lineHeight: '28px', + fontWeight: 600, }, h4: { fontSize: '16px', lineHeight: '22px', + fontWeight: 600, }, page: { fontSize: '36px', lineHeight: '44px', + fontWeight: 600, }, callout: { fontSize: '36px', @@ -221,6 +229,7 @@ export const Theme = { articleTitle: { fontSize: '36px', lineHeight: '54px', + fontWeight: 600, }, }, shadows: { From 7c076ea567a26ca1f7b5c9a814c7f465f79a34d8 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 18:35:31 +0800 Subject: [PATCH 12/33] fix: style ui --- .../src/blocks/page/PageView.tsx | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libs/components/editor-blocks/src/blocks/page/PageView.tsx b/libs/components/editor-blocks/src/blocks/page/PageView.tsx index 5aca7e646..17ad6cae9 100644 --- a/libs/components/editor-blocks/src/blocks/page/PageView.tsx +++ b/libs/components/editor-blocks/src/blocks/page/PageView.tsx @@ -109,12 +109,15 @@ export const PageView: FC = ({ 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', + }, + }; }); From b1602b2b1a4c35fb29894e53e1b41ed19fb30a97 Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 18:39:01 +0800 Subject: [PATCH 13/33] fix: error catch (#182) --- .../editor-plugins/src/menu/command-menu/Menu.tsx | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index 38a397a8a..67261c813 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -123,17 +123,13 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { const COMMAND_MENU_HEIGHT = window.innerHeight * 0.4; - const { top, left } = + const { top, left, bottom } = editor.container.getBoundingClientRect(); if (clientHeight - rectTop <= COMMAND_MENU_HEIGHT) { setCommandMenuPosition({ left: rect.left - left, - top: - rectTop - - top - - COMMAND_MENU_HEIGHT + - 20, - bottom: 'initial', + bottom: bottom - rect.bottom + 24, + top: 'initial', }); } else { setCommandMenuPosition({ From d7ddffe3f8f9530f4841c29e346710688e17959e Mon Sep 17 00:00:00 2001 From: DiamondThree Date: Wed, 10 Aug 2022 18:45:20 +0800 Subject: [PATCH 14/33] fix: error catch (#181) --- libs/components/editor-plugins/src/menu/command-menu/Menu.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx index 67261c813..7b74cbfd5 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -81,7 +81,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { const checkIfShowCommandMenu = useCallback( async (event: React.KeyboardEvent) => { const { type, anchorNode } = editor.selection.currentSelectInfo; - // console.log(await editor.getBlockById(anchorNode.id)); + if (!anchorNode?.id) { + return; + } const activeBlock = await editor.getBlockById(anchorNode.id); if (activeBlock.type === Protocol.Block.Type.page) { return; From 89191290e43d5838c31f75d05ac73b5197c149db Mon Sep 17 00:00:00 2001 From: DarkSky Date: Wed, 10 Aug 2022 22:10:34 +0800 Subject: [PATCH 15/33] refactor: remote data provider --- libs/datasource/jwt-rpc/src/indexeddb.ts | 2 +- libs/datasource/jwt-rpc/src/sqlite.ts | 1 + libs/datasource/jwt/src/adapter/yjs/index.ts | 152 ++++++------------ .../jwt/src/adapter/yjs/provider.ts | 75 +++++++++ libs/datasource/jwt/src/index.ts | 18 ++- 5 files changed, 137 insertions(+), 111 deletions(-) create mode 100644 libs/datasource/jwt/src/adapter/yjs/provider.ts diff --git a/libs/datasource/jwt-rpc/src/indexeddb.ts b/libs/datasource/jwt-rpc/src/indexeddb.ts index bb38fb8a8..512daea6d 100644 --- a/libs/datasource/jwt-rpc/src/indexeddb.ts +++ b/libs/datasource/jwt-rpc/src/indexeddb.ts @@ -134,7 +134,7 @@ export class IndexedDBProvider extends Observable { } /** - * Destroys this instance and removes all data from SQLite. + * Destroys this instance and removes all data from indexeddb. * * @return {Promise} */ diff --git a/libs/datasource/jwt-rpc/src/sqlite.ts b/libs/datasource/jwt-rpc/src/sqlite.ts index 36e374efe..0d47c4615 100644 --- a/libs/datasource/jwt-rpc/src/sqlite.ts +++ b/libs/datasource/jwt-rpc/src/sqlite.ts @@ -41,6 +41,7 @@ const initSQLiteInstance = async () => { _sqliteProcessing = true; _sqliteInstance = await sqlite({ locateFile: () => + // @ts-ignore new URL('sql.js/dist/sql-wasm.wasm', import.meta.url).href, }); _sqliteProcessing = false; diff --git a/libs/datasource/jwt/src/adapter/yjs/index.ts b/libs/datasource/jwt/src/adapter/yjs/index.ts index d815fe473..2e7681ae5 100644 --- a/libs/datasource/jwt/src/adapter/yjs/index.ts +++ b/libs/datasource/jwt/src/adapter/yjs/index.ts @@ -18,11 +18,7 @@ import { snapshot, } from 'yjs'; -import { - IndexedDBProvider, - SQLiteProvider, - WebsocketProvider, -} from '@toeverything/datasource/jwt-rpc'; +import { IndexedDBProvider } from '@toeverything/datasource/jwt-rpc'; import { AsyncDatabaseAdapter, @@ -31,7 +27,7 @@ import { Connectivity, HistoryManager, } from '../../adapter'; -import { BucketBackend, BlockItem, BlockTypes } from '../../types'; +import { BlockItem, BlockTypes } from '../../types'; import { getLogger, sha3, sleep } from '../../utils'; import { YjsRemoteBinaries } from './binary'; @@ -43,51 +39,26 @@ import { } from './operation'; import { EmitEvents, Suspend } from './listener'; import { YjsHistoryManager } from './history'; +import { YjsProvider } from './provider'; declare const JWT_DEV: boolean; const logger = getLogger('BlockDB:yjs'); +type ConnectivityListener = ( + workspace: string, + connectivity: Connectivity +) => void; type YjsProviders = { awareness: Awareness; idb: IndexedDBProvider; binariesIdb: IndexedDBProvider; - fstore?: SQLiteProvider; - ws?: WebsocketProvider; - backend: string; gatekeeper: GateKeeper; + connListener: { listeners?: ConnectivityListener }; userId: string; remoteToken?: string; // remote storage token }; const _yjsDatabaseInstance = new Map(); -async function _initWebsocketProvider( - url: string, - room: string, - doc: Doc, - token?: string, - params?: YjsInitOptions['params'] -): Promise<[Awareness, WebsocketProvider | undefined]> { - const awareness = new Awareness(doc); - - if (token) { - const ws = new WebsocketProvider(token, url, room, doc, { - awareness, - params, - }) as any; // TODO: type is erased after cascading references - - // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later - return new Promise((resolve, reject) => { - // TODO: synced will also be triggered on reconnection after losing sync - // There needs to be an event mechanism to emit the synchronization state to the upper layer - ws.once('synced', () => resolve([awareness, ws])); - ws.once('lost-connection', () => resolve([awareness, ws])); - ws.once('connection-error', () => reject()); - }); - } else { - return [awareness, undefined]; - } -} - const _asyncInitLoading = new Set(); const _waitLoading = async (workspace: string) => { while (_asyncInitLoading.has(workspace)) { @@ -96,14 +67,11 @@ const _waitLoading = async (workspace: string) => { }; async function _initYjsDatabase( - backend: string, workspace: string, options: { - params: YjsInitOptions['params']; userId: string; token?: string; - importData?: Uint8Array; - exportData?: (binary: Uint8Array) => void; + provider?: Record; } ): Promise { if (_asyncInitLoading.has(workspace)) { @@ -119,28 +87,10 @@ async function _initYjsDatabase( } // if (instance) return instance; _asyncInitLoading.add(workspace); - const { params, userId, token: remoteToken } = options; + const { userId, token } = options; const doc = new Doc({ autoLoad: true, shouldLoad: true }); - - const idbp = new IndexedDBProvider(workspace, doc).whenSynced; - - const fs = new SQLiteProvider(workspace, doc, options.importData); - if (options.exportData) fs.registerExporter(options.exportData); - - const wsp = _initWebsocketProvider( - backend, - workspace, - doc, - remoteToken, - params - ); - - const [idb, [awareness, ws], fstore] = await Promise.all([ - idbp, - wsp, - fs.whenSynced, - ]); + const idb = await new IndexedDBProvider(workspace, doc).whenSynced; const binaries = new Doc({ autoLoad: true, shouldLoad: true }); const binariesIdb = await new IndexedDBProvider( @@ -148,6 +98,8 @@ async function _initYjsDatabase( binaries ).whenSynced; + const awareness = new Awareness(doc); + const gateKeeperData = doc.getMap>('gatekeeper'); const gatekeeper = new GateKeeper( @@ -157,44 +109,45 @@ async function _initYjsDatabase( gateKeeperData.get('common') || gateKeeperData.set('common', new YMap()) ); - _yjsDatabaseInstance.set(workspace, { + const connListener: { listeners?: ConnectivityListener } = {}; + if (options.provider) { + const emitState = (c: Connectivity) => + connListener.listeners?.(workspace, c); + await Promise.all( + Object.entries(options.provider).map(async ([, p]) => + p({ awareness, doc, token, workspace, emitState }) + ) + ); + } + const newInstance = { awareness, idb, binariesIdb, - fstore, - ws, - backend, gatekeeper, + connListener, userId, - remoteToken, - }); + remoteToken: token, + }; + + _yjsDatabaseInstance.set(workspace, newInstance); + _asyncInitLoading.delete(workspace); - return { - awareness, - idb, - binariesIdb, - fstore, - ws, - backend, - gatekeeper, - userId, - remoteToken, - }; + return newInstance; } export type { YjsBlockInstance } from './block'; export type { YjsContentOperation } from './operation'; export type YjsInitOptions = { - backend: typeof BucketBackend[keyof typeof BucketBackend]; - params?: Record; userId?: string; token?: string; - importData?: Uint8Array; - exportData?: (binary: Uint8Array) => void; + provider?: Record; }; +export { getYjsProviders } from './provider'; +export type { YjsProviderOptions } from './provider'; + export class YjsAdapter implements AsyncDatabaseAdapter { private readonly _provider: YjsProviders; private readonly _doc: Doc; // doc instance @@ -217,20 +170,11 @@ export class YjsAdapter implements AsyncDatabaseAdapter { workspace: string, options: YjsInitOptions ): Promise { - const { - backend, - params = {}, - userId = 'default', - token, - importData, - exportData, - } = options; - const providers = await _initYjsDatabase(backend, workspace, { - params, + const { userId = 'default', token, provider } = options; + const providers = await _initYjsDatabase(workspace, { userId, token, - importData, - exportData, + provider, }); return new YjsAdapter(providers); } @@ -255,18 +199,14 @@ export class YjsAdapter implements AsyncDatabaseAdapter { this._listener = new Map(); - const ws = providers.ws as any; - if (ws) { - const workspace = providers.idb.name; - const emitState = (connectivity: Connectivity) => { - this._listener.get('connectivity')?.( - new Map([[workspace, connectivity]]) - ); - }; - ws.on('synced', () => emitState('connected')); - ws.on('lost-connection', () => emitState('retry')); - ws.on('connection-error', () => emitState('retry')); - } + providers.connListener.listeners = ( + workspace: string, + connectivity: Connectivity + ) => { + this._listener.get('connectivity')?.( + new Map([[workspace, connectivity]]) + ); + }; const debounced_editing_notifier = debounce( () => { diff --git a/libs/datasource/jwt/src/adapter/yjs/provider.ts b/libs/datasource/jwt/src/adapter/yjs/provider.ts new file mode 100644 index 000000000..d0f7f9d3a --- /dev/null +++ b/libs/datasource/jwt/src/adapter/yjs/provider.ts @@ -0,0 +1,75 @@ +import { Doc } from 'yjs'; +import { Awareness } from 'y-protocols/awareness.js'; + +import { + SQLiteProvider, + WebsocketProvider, +} from '@toeverything/datasource/jwt-rpc'; + +import { Connectivity } from '../../adapter'; +import { BucketBackend } from '../../types'; + +type YjsDefaultInstances = { + awareness: Awareness; + doc: Doc; + token?: string; + workspace: string; + emitState: (connectivity: Connectivity) => void; +}; + +export type YjsProvider = (instances: YjsDefaultInstances) => Promise; + +export type YjsProviderOptions = { + backend: typeof BucketBackend[keyof typeof BucketBackend]; + params?: Record; + importData?: Uint8Array; + exportData?: (binary: Uint8Array) => void; +}; + +export const getYjsProviders = ( + options: YjsProviderOptions +): Record => { + return { + sqlite: async (instances: YjsDefaultInstances) => { + const fs = new SQLiteProvider( + instances.workspace, + instances.doc, + options.importData + ); + if (options.exportData) fs.registerExporter(options.exportData); + await fs.whenSynced; + }, + ws: async (instances: YjsDefaultInstances) => { + if (instances.token) { + const ws = new WebsocketProvider( + instances.token, + options.backend, + instances.workspace, + instances.doc, + { + awareness: instances.awareness, + params: options.params, + } + ) as any; // TODO: type is erased after cascading references + + // Wait for ws synchronization to complete, otherwise the data will be modified in reverse, which can be optimized later + return new Promise((resolve, reject) => { + // TODO: synced will also be triggered on reconnection after losing sync + // There needs to be an event mechanism to emit the synchronization state to the upper layer + ws.once('synced', () => resolve()); + ws.once('lost-connection', () => resolve()); + ws.once('connection-error', () => reject()); + ws.on('synced', () => instances.emitState('connected')); + ws.on('lost-connection', () => + instances.emitState('retry') + ); + ws.on('connection-error', () => + instances.emitState('retry') + ); + }); + } else { + return; + } + }, + }; +}; diff --git a/libs/datasource/jwt/src/index.ts b/libs/datasource/jwt/src/index.ts index 6ced321c2..acd3ce5f5 100644 --- a/libs/datasource/jwt/src/index.ts +++ b/libs/datasource/jwt/src/index.ts @@ -15,7 +15,11 @@ import { ContentTypes, Connectivity, } from './adapter'; -import { YjsBlockInstance } from './adapter/yjs'; +import { + getYjsProviders, + YjsBlockInstance, + YjsProviderOptions, +} from './adapter/yjs'; import { BaseBlock, BlockIndexer, @@ -27,11 +31,11 @@ import { BlockTypes, BlockTypeKeys, BlockFlavors, - BucketBackend, UUID, BlockFlavorKeys, BlockItem, ExcludeFunction, + BucketBackend, } from './types'; import { BlockEventBus, genUUID, getLogger } from './utils'; @@ -588,10 +592,16 @@ export class BlockClient< public static async init( workspace: string, - options: Partial = {} + options: Partial< + YjsInitOptions & YjsProviderOptions & BlockClientOptions + > = {} ): Promise { const instance = await YjsAdapter.init(workspace, { - backend: BucketBackend.YjsWebSocketAffine, + provider: getYjsProviders({ + backend: BucketBackend.YjsWebSocketAffine, + exportData: console.log.bind(console), + ...options, + }), ...options, }); return new BlockClient(instance, workspace, options); From 86090be4a3b217db576ea3e1655ad5d6ee9432f4 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 11 Aug 2022 01:45:38 +0800 Subject: [PATCH 16/33] refactor: local storage --- .../src/pages/workspace/docs/Page.tsx | 13 ++- libs/components/account/src/login/fs.tsx | 99 +++++++++++++++++++ libs/components/account/src/login/index.tsx | 4 +- .../layout/src/header/LayoutHeader.tsx | 1 + .../db-service/src/services/base.ts | 8 ++ .../db-service/src/services/database/index.ts | 9 ++ libs/datasource/jwt-rpc/src/sqlite.ts | 80 +++++++++++---- libs/datasource/jwt/src/adapter/index.ts | 28 ++++++ libs/datasource/jwt/src/adapter/yjs/index.ts | 50 ++++++---- .../jwt/src/adapter/yjs/provider.ts | 26 +++-- libs/datasource/jwt/src/index.ts | 31 +++++- libs/datasource/state/src/user.ts | 43 +++++--- 12 files changed, 318 insertions(+), 74 deletions(-) create mode 100644 libs/components/account/src/login/fs.tsx diff --git a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx index 76c48efeb..886d8f1c1 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx @@ -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'; diff --git a/libs/components/account/src/login/fs.tsx b/libs/components/account/src/login/fs.tsx new file mode 100644 index 000000000..bc51a9087 --- /dev/null +++ b/libs/components/account/src/login/fs.tsx @@ -0,0 +1,99 @@ +/* eslint-disable filename-rules/match */ +import { useState } from 'react'; + +import { LogoImg } from '@toeverything/components/common'; +import { + MuiButton, + MuiBox, + MuiGrid, + MuiSnackbar, +} from '@toeverything/components/ui'; +import { services } from '@toeverything/datasource/db-service'; +import { useLocalTrigger } from '@toeverything/datasource/state'; + +import { Error } from './../error'; + +const requestPermission = async (workspace: string) => { + indexedDB.deleteDatabase(workspace); + 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 = () => { + const onSelected = useLocalTrigger(); + const [error, setError] = useState(false); + return ( + + + + + + + + { + try { + await requestPermission('AFFiNE'); + onSelected(); + } catch (e) { + setError(true); + setTimeout(() => setError(false), 3000); + } + }} + style={{ + textAlign: 'center', + width: '300px', + margin: '300px auto 20px auto', + }} + sx={{ mt: 1 }} + > + + + + Sync to Disk + + + + + ); +}; diff --git a/libs/components/account/src/login/index.tsx b/libs/components/account/src/login/index.tsx index 3601d97b9..ba7a589c8 100644 --- a/libs/components/account/src/login/index.tsx +++ b/libs/components/account/src/login/index.tsx @@ -1,11 +1,13 @@ +/* eslint-disable filename-rules/match */ // import { Authing } from './authing'; import { Firebase } from './firebase'; +import { FileSystem } from './fs'; export function Login() { return ( <> {/* */} - + {process.env['NX_LOCAL'] ? : } ); } diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index 0d9560666..978b7f8df 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -6,6 +6,7 @@ import { SideBarViewCloseIcon, } from '@toeverything/components/icons'; import { useShowSettingsSidebar } from '@toeverything/datasource/state'; + import { CurrentPageTitle } from './Title'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; diff --git a/libs/datasource/db-service/src/services/base.ts b/libs/datasource/db-service/src/services/base.ts index dcf53dcb4..9e10af917 100644 --- a/libs/datasource/db-service/src/services/base.ts +++ b/libs/datasource/db-service/src/services/base.ts @@ -154,6 +154,14 @@ export abstract class ServiceBaseClass { await this.database.unregisterTagExporter(workspace, name); } + async setupDataExporter( + workspace: string, + initialData: Uint8Array, + cb: (data: Uint8Array) => Promise + ) { + await this.database.setupDataExporter(workspace, initialData, cb); + } + protected async _observe( workspace: string, blockId: string, diff --git a/libs/datasource/db-service/src/services/database/index.ts b/libs/datasource/db-service/src/services/database/index.ts index 439e6ca90..9f97b9a35 100644 --- a/libs/datasource/db-service/src/services/database/index.ts +++ b/libs/datasource/db-service/src/services/database/index.ts @@ -192,4 +192,13 @@ export class Database { } } } + + async setupDataExporter( + workspace: string, + initialData: Uint8Array, + callback: (binary: Uint8Array) => Promise + ) { + const db = await this.getDatabase(workspace); + await db.setupDataExporter(initialData, callback); + } } diff --git a/libs/datasource/jwt-rpc/src/sqlite.ts b/libs/datasource/jwt-rpc/src/sqlite.ts index 0d47c4615..3a33daa75 100644 --- a/libs/datasource/jwt-rpc/src/sqlite.ts +++ b/libs/datasource/jwt-rpc/src/sqlite.ts @@ -5,7 +5,7 @@ import { Observable } from 'lib0/observable.js'; const PREFERRED_TRIM_SIZE = 500; const _stmts = { - create: 'CREATE TABLE updates (key INTEGER PRIMARY KEY AUTOINCREMENT, value BLOB);', + create: 'CREATE TABLE IF NOT EXISTS updates (key INTEGER PRIMARY KEY AUTOINCREMENT, value BLOB);', selectAll: 'SELECT * FROM updates where key >= $idx', selectCount: 'SELECT count(*) FROM updates', insert: 'INSERT INTO updates VALUES (null, $data);', @@ -59,7 +59,7 @@ export class SQLiteProvider extends Observable { private _size: number; private _destroyed: boolean; private _db: Promise; - private _saver?: (binary: Uint8Array) => void; + private _saver?: (binary: Uint8Array) => Promise | undefined; private _destroy: () => void; constructor(name: string, doc: Y.Doc, origin?: Uint8Array) { @@ -82,8 +82,9 @@ export class SQLiteProvider extends Observable { this.whenSynced = this._db.then(async db => { this.db = db; const currState = Y.encodeStateAsUpdate(doc); - await this._fetchUpdates(); + await this._fetchUpdates(true); db.exec(_stmts.insert, { $data: currState }); + this._storeState(); if (this._destroyed) return this; this.emit('synced', [this]); this.synced = true; @@ -91,21 +92,38 @@ export class SQLiteProvider extends Observable { }); // Timeout in ms until data is merged and persisted in sqlite. - const storeTimeout = 1000; + const storeTimeout = 500; let storeTimeoutId: NodeJS.Timer | undefined = undefined; + let lastSize = 0; + + const debouncedStoreState = (force = false) => { + // debounce store call + if (storeTimeoutId) clearTimeout(storeTimeoutId); + + if (force) { + if (lastSize !== this._size) { + this._storeState(); + storeTimeoutId = undefined; + lastSize = this._size; + } + } else { + storeTimeoutId = setTimeout(() => { + this._storeState(); + storeTimeoutId = undefined; + }, storeTimeout); + } + }; + const storeStateInterval = setInterval( + () => debouncedStoreState(true), + 1000 + ); const storeUpdate = (update: Uint8Array, origin: any) => { if (this._saver && this.db && origin !== this) { this.db.exec(_stmts.insert, { $data: update }); if (++this._size >= PREFERRED_TRIM_SIZE) { - // debounce store call - if (storeTimeoutId) clearTimeout(storeTimeoutId); - - storeTimeoutId = setTimeout(() => { - this._storeState(); - storeTimeoutId = undefined; - }, storeTimeout); + debouncedStoreState(); } } }; @@ -116,34 +134,53 @@ export class SQLiteProvider extends Observable { this._destroy = () => { if (storeTimeoutId) clearTimeout(storeTimeoutId); + if (storeStateInterval) clearInterval(storeStateInterval); this.doc.off('update', storeUpdate); this.doc.off('destroy', this.destroy); }; } - registerExporter(saver: (binary: Uint8Array) => void) { + registerExporter(saver: (binary: Uint8Array) => Promise | undefined) { this._saver = saver; } - private async _storeState() { + private async _storeState(force?: boolean) { await this._fetchUpdates(); - if (this.db && this._size >= PREFERRED_TRIM_SIZE) { - this.db.exec(_stmts.insert, { - $data: Y.encodeStateAsUpdate(this.doc), - }); + if (this.db) { + if (force || this._size >= PREFERRED_TRIM_SIZE) { + this.db.exec(_stmts.insert, { + $data: Y.encodeStateAsUpdate(this.doc), + }); - clearUpdates(this.db, this._ref); + clearUpdates(this.db, this._ref); - this._size = countUpdates(this.db); + this._size = countUpdates(this.db); + } - this._saver?.(this.db?.export()); + await this._saver?.(this.db?.export()); } } - private async _fetchUpdates() { + private _waitUpdate(sync = false) { + if (sync) { + return new Promise((resolve, reject) => { + const final = (_: any, origin: any) => { + if (origin === this) { + this.doc.off('update', final); + resolve(); + } + }; + this.doc.on('update', final); + }); + } + return undefined; + } + + private async _fetchUpdates(sync = false) { if (this.db) { + const wait = this._waitUpdate(sync); const updates = getAllUpdates(this.db, this._ref); Y.transact( @@ -160,6 +197,7 @@ export class SQLiteProvider extends Observable { const lastKey = Math.max(...updates.map(([idx]) => idx)); this._ref = lastKey + 1; this._size = countUpdates(this.db); + await wait; } } diff --git a/libs/datasource/jwt/src/adapter/index.ts b/libs/datasource/jwt/src/adapter/index.ts index ddf4f29ca..2fa2ec551 100644 --- a/libs/datasource/jwt/src/adapter/index.ts +++ b/libs/datasource/jwt/src/adapter/index.ts @@ -136,6 +136,7 @@ interface BlockInstance { interface AsyncDatabaseAdapter { inspector(): Record; + reload(): void; createBlock( options: Pick, 'type' | 'flavor'> & { binary?: ArrayBuffer; @@ -156,6 +157,33 @@ interface AsyncDatabaseAdapter { getUserId(): string; } +export type DataExporter = (binary: Uint8Array) => Promise; + +export const getDataExporter = () => { + let exporter: DataExporter | undefined = undefined; + let importer: (() => Uint8Array | undefined) | undefined = undefined; + + const importData = () => importer?.(); + const exportData = (binary: Uint8Array) => exporter?.(binary); + const hasExporter = () => !!exporter; + + const installExporter = ( + initialData: Uint8Array | undefined, + cb: DataExporter + ) => { + return new Promise(resolve => { + importer = () => initialData; + exporter = async (data: Uint8Array) => { + exporter = cb; + await cb(data); + resolve(); + }; + }); + }; + + return { importData, exportData, hasExporter, installExporter }; +}; + export type { AsyncDatabaseAdapter, BlockPosition, diff --git a/libs/datasource/jwt/src/adapter/yjs/index.ts b/libs/datasource/jwt/src/adapter/yjs/index.ts index 2e7681ae5..a1497de60 100644 --- a/libs/datasource/jwt/src/adapter/yjs/index.ts +++ b/libs/datasource/jwt/src/adapter/yjs/index.ts @@ -153,19 +153,21 @@ export class YjsAdapter implements AsyncDatabaseAdapter { private readonly _doc: Doc; // doc instance private readonly _awareness: Awareness; // lightweight state synchronization private readonly _gatekeeper: GateKeeper; // Simple access control - private readonly _history: YjsHistoryManager; + private readonly _history!: YjsHistoryManager; // Block Collection // key is a randomly generated global id - private readonly _blocks: YMap>; - private readonly _blockUpdated: YMap; + private readonly _blocks!: YMap>; + private readonly _blockUpdated!: YMap; // Maximum cache Block 1024, ttl 10 minutes - private readonly _blockCaches: LRUCache; + private readonly _blockCaches!: LRUCache; - private readonly _binaries: YjsRemoteBinaries; + private readonly _binaries!: YjsRemoteBinaries; private readonly _listener: Map>; + private readonly _reload: () => void; + static async init( workspace: string, options: YjsInitOptions @@ -184,18 +186,28 @@ export class YjsAdapter implements AsyncDatabaseAdapter { this._doc = providers.idb.doc; this._awareness = providers.awareness; this._gatekeeper = providers.gatekeeper; - - const blocks = this._doc.getMap>('blocks'); - this._blocks = - blocks.get('content') || blocks.set('content', new YMap()); - this._blockUpdated = - blocks.get('updated') || blocks.set('updated', new YMap()); - this._blockCaches = new LRUCache({ max: 1024, ttl: 1000 * 60 * 10 }); - this._binaries = new YjsRemoteBinaries( - providers.binariesIdb.doc.getMap(), - providers.remoteToken - ); - this._history = new YjsHistoryManager(this._blocks); + this._reload = () => { + const blocks = this._doc.getMap>('blocks'); + // @ts-ignore + this._blocks = + blocks.get('content') || blocks.set('content', new YMap()); + // @ts-ignore + this._blockUpdated = + blocks.get('updated') || blocks.set('updated', new YMap()); + // @ts-ignore + this._blockCaches = new LRUCache({ + max: 1024, + ttl: 1000 * 60 * 10, + }); + // @ts-ignore + this._binaries = new YjsRemoteBinaries( + providers.binariesIdb.doc.getMap(), + providers.remoteToken + ); + // @ts-ignore + this._history = new YjsHistoryManager(this._blocks); + }; + this._reload(); this._listener = new Map(); @@ -281,6 +293,10 @@ export class YjsAdapter implements AsyncDatabaseAdapter { }); } + reload() { + this._reload(); + } + getUserId(): string { return this._provider.userId; } diff --git a/libs/datasource/jwt/src/adapter/yjs/provider.ts b/libs/datasource/jwt/src/adapter/yjs/provider.ts index d0f7f9d3a..f7068288f 100644 --- a/libs/datasource/jwt/src/adapter/yjs/provider.ts +++ b/libs/datasource/jwt/src/adapter/yjs/provider.ts @@ -22,8 +22,9 @@ export type YjsProvider = (instances: YjsDefaultInstances) => Promise; export type YjsProviderOptions = { backend: typeof BucketBackend[keyof typeof BucketBackend]; params?: Record; - importData?: Uint8Array; - exportData?: (binary: Uint8Array) => void; + importData?: () => Promise | Uint8Array | undefined; + exportData?: (binary: Uint8Array) => Promise | undefined; + hasExporter?: () => boolean; }; export const getYjsProviders = ( @@ -31,13 +32,20 @@ export const getYjsProviders = ( ): Record => { return { sqlite: async (instances: YjsDefaultInstances) => { - const fs = new SQLiteProvider( - instances.workspace, - instances.doc, - options.importData - ); - if (options.exportData) fs.registerExporter(options.exportData); - await fs.whenSynced; + const fsHandle = setInterval(async () => { + if (options.hasExporter?.()) { + clearInterval(fsHandle); + const fs = new SQLiteProvider( + instances.workspace, + instances.doc, + await options.importData?.() + ); + if (options.exportData) { + fs.registerExporter(options.exportData); + } + await fs.whenSynced; + } + }, 500); }, ws: async (instances: YjsDefaultInstances) => { if (instances.token) { diff --git a/libs/datasource/jwt/src/index.ts b/libs/datasource/jwt/src/index.ts index acd3ce5f5..ecad7aebf 100644 --- a/libs/datasource/jwt/src/index.ts +++ b/libs/datasource/jwt/src/index.ts @@ -14,6 +14,8 @@ import { HistoryManager, ContentTypes, Connectivity, + DataExporter, + getDataExporter, } from './adapter'; import { getYjsProviders, @@ -66,6 +68,10 @@ type BlockClientOptions = { content?: BlockExporters; metadata?: BlockExporters>; tagger?: BlockExporters; + installExporter: ( + initialData: Uint8Array, + exporter: DataExporter + ) => Promise; }; export class BlockClient< @@ -95,10 +101,15 @@ export class BlockClient< private readonly _root: { node?: BaseBlock }; + private readonly _installExporter: ( + initialData: Uint8Array, + exporter: DataExporter + ) => Promise; + private constructor( adapter: A, workspace: string, - options?: BlockClientOptions + options: BlockClientOptions ) { this._adapter = adapter; this._workspace = workspace; @@ -142,6 +153,7 @@ export class BlockClient< }); this._root = {}; + this._installExporter = options.installExporter; } public addBlockListener(tag: string, listener: BlockListener) { @@ -590,21 +602,34 @@ export class BlockClient< return this._adapter.history(); } + public async setupDataExporter(initialData: Uint8Array, cb: DataExporter) { + await this._installExporter(initialData, cb); + this._adapter.reload(); + } + public static async init( workspace: string, options: Partial< YjsInitOptions & YjsProviderOptions & BlockClientOptions > = {} ): Promise { + const { importData, exportData, hasExporter, installExporter } = + getDataExporter(); + const instance = await YjsAdapter.init(workspace, { provider: getYjsProviders({ backend: BucketBackend.YjsWebSocketAffine, - exportData: console.log.bind(console), + importData, + exportData, + hasExporter, ...options, }), ...options, }); - return new BlockClient(instance, workspace, options); + return new BlockClient(instance, workspace, { + ...options, + installExporter, + }); } } diff --git a/libs/datasource/state/src/user.ts b/libs/datasource/state/src/user.ts index bce11b832..fefb689f0 100644 --- a/libs/datasource/state/src/user.ts +++ b/libs/datasource/state/src/user.ts @@ -55,28 +55,39 @@ const _useUserAndSpace = () => { const currentSpaceId: string | undefined = useMemo(() => user?.id, [user]); - return { - user, - currentSpaceId, - loading, - }; + return { user, currentSpaceId, loading }; }; +const BRAND_ID = 'AFFiNE'; + +const _localTrigger = atom(false); const _useUserAndSpacesForFreeLogin = () => { + const [user, setUser] = useAtom(_userAtom); const [loading, setLoading] = useAtom(_loadingAtom); + const [localTrigger] = useAtom(_localTrigger); useEffect(() => setLoading(false), []); - const BRAND_ID = 'AFFiNE'; - return { - user: { - photo: '', - id: BRAND_ID, - nickname: BRAND_ID, - email: '', - } as UserInfo, - currentSpaceId: BRAND_ID, - loading, - }; + + useEffect(() => { + if (localTrigger) { + setUser({ + photo: '', + id: BRAND_ID, + username: BRAND_ID, + nickname: BRAND_ID, + email: '', + }); + } + }, [localTrigger, setLoading, setUser]); + + const currentSpaceId: string | undefined = useMemo(() => user?.id, [user]); + + return { user, currentSpaceId, loading }; +}; + +export const useLocalTrigger = () => { + const [, setTrigger] = useAtom(_localTrigger); + return () => setTrigger(true); }; export const useUserAndSpaces = process.env['NX_LOCAL'] From a32abb43c52f240f012b5a8d31d313968b10be13 Mon Sep 17 00:00:00 2001 From: alt0 Date: Thu, 11 Aug 2022 10:35:38 +0800 Subject: [PATCH 17/33] docs: update docs link --- CONTRIBUTING.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc63bee96..7b7b63a1a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,4 +85,4 @@ 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://affine.gitbook.io/affine/) +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/) diff --git a/README.md b/README.md index 5018d0ba4..8076877f0 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066 # How to use -If you have experience in front-end development, you may wish to refer to our [documentation](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine) to learn more about deploying your own version or contributing further to development. For those intersting in trying our latest version, please bear with us as we are planning to launch a web version soon. +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. For those intersting in trying our latest version, please bear with us as we are planning to launch a web version soon. 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. @@ -91,7 +91,7 @@ Affine is fully built with web technologies to ensure consistency and accessibil # Documentation -AFFiNE is not yet ready for production use. For installation, you may check how to build or deploy AFFiNE from our [quick-start](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine/quick-start) guide. Alternatively, you can view our [full documentation](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 From e9447cdcfe0258ae39a9219294f6753793cdf5a1 Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 11 Aug 2022 10:44:22 +0800 Subject: [PATCH 18/33] fix: load data on first time --- libs/datasource/jwt-rpc/src/sqlite.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/datasource/jwt-rpc/src/sqlite.ts b/libs/datasource/jwt-rpc/src/sqlite.ts index 3a33daa75..1753edda8 100644 --- a/libs/datasource/jwt-rpc/src/sqlite.ts +++ b/libs/datasource/jwt-rpc/src/sqlite.ts @@ -163,8 +163,8 @@ export class SQLiteProvider extends Observable { } } - private _waitUpdate(sync = false) { - if (sync) { + private _waitUpdate(updates: any[], sync = false) { + if (updates.length && sync) { return new Promise((resolve, reject) => { const final = (_: any, origin: any) => { if (origin === this) { @@ -180,8 +180,8 @@ export class SQLiteProvider extends Observable { private async _fetchUpdates(sync = false) { if (this.db) { - const wait = this._waitUpdate(sync); const updates = getAllUpdates(this.db, this._ref); + const wait = this._waitUpdate(updates, sync); Y.transact( this.doc, From 083d74c904d736b472c907a7e9dfcd296c6f64df Mon Sep 17 00:00:00 2001 From: DarkSky Date: Thu, 11 Aug 2022 10:57:58 +0800 Subject: [PATCH 19/33] fix: e2e --- libs/components/account/src/login/fs.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libs/components/account/src/login/fs.tsx b/libs/components/account/src/login/fs.tsx index bc51a9087..99f1252db 100644 --- a/libs/components/account/src/login/fs.tsx +++ b/libs/components/account/src/login/fs.tsx @@ -1,5 +1,5 @@ /* eslint-disable filename-rules/match */ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import { LogoImg } from '@toeverything/components/common'; import { @@ -46,6 +46,13 @@ const requestPermission = async (workspace: string) => { export const FileSystem = () => { const onSelected = useLocalTrigger(); const [error, setError] = useState(false); + + useEffect(() => { + if (process.env['NX_E2E']) { + onSelected(); + } + }, []); + return ( Date: Thu, 11 Aug 2022 11:18:44 +0800 Subject: [PATCH 20/33] fix: console error (#180) --- .../src/pages/workspace/docs/components/tabs/Tabs.tsx | 7 ++++--- .../src/workspace-sidebar/page-tree/tree-item/styles.ts | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx index 2d56e7edc..7f442caca 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx @@ -13,9 +13,10 @@ const StyledTabs = styled('div')(({ theme }) => { }; }); -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; diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts index a615a497e..e05415ce8 100644 --- a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts +++ b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts @@ -168,7 +168,9 @@ export const TreeItemMoreActions = styled('div')` visibility: hidden; `; -export const TextLink = styled(Link)<{ active?: boolean }>` +export const TextLink = styled(Link, { + shouldForwardProp: (prop: string) => !['active'].includes(prop), +})<{ active?: boolean }>` display: flex; align-items: center; flex-grow: 1; From 7c15f704e0a490520d2607cd51fed05d5f5bd5a3 Mon Sep 17 00:00:00 2001 From: austaras Date: Thu, 11 Aug 2022 11:44:10 +0800 Subject: [PATCH 21/33] chore: typo --- apps/ligo-virgo/webpack.config.js | 4 ++-- apps/venus/webpack.config.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/ligo-virgo/webpack.config.js b/apps/ligo-virgo/webpack.config.js index 9838e4dfb..2082de534 100644 --- a/apps/ligo-virgo/webpack.config.js +++ b/apps/ligo-virgo/webpack.config.js @@ -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', }, diff --git a/apps/venus/webpack.config.js b/apps/venus/webpack.config.js index c32585114..88df817a1 100644 --- a/apps/venus/webpack.config.js +++ b/apps/venus/webpack.config.js @@ -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', }, From 4dd76949c4e3d678bc5a694b91d0b5c007dd1968 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 12:19:36 +0800 Subject: [PATCH 22/33] refactor: change pendant popover trigger to click --- .../src/blocks/group/scene-kanban/CardContext.tsx | 3 +++ .../src/block-pendant/pendant-render/PandentRender.tsx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx index f7f05b37d..13fcaa45e 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx @@ -60,6 +60,9 @@ export const CardContext = (props: Props) => { const StyledCardContainer = styled('div')` cursor: pointer; + &:hover { + z-index: 1; + } &:focus-within { z-index: 1; } diff --git a/libs/components/editor-core/src/block-pendant/pendant-render/PandentRender.tsx b/libs/components/editor-core/src/block-pendant/pendant-render/PandentRender.tsx index 76ed0d4e3..8de0e9a20 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-render/PandentRender.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-render/PandentRender.tsx @@ -105,6 +105,8 @@ export const PendantRender = ({ block }: { block: AsyncBlock }) => { From 52a59d8dfd30d6c2cdcb7e0de6931cfdc2a1caa1 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 14:06:31 +0800 Subject: [PATCH 23/33] fix: add message after link copied, fixed #131 --- .../layout/src/settings-sidebar/Settings/use-settings.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts index 1ce5bcb55..639afef92 100644 --- a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts +++ b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts @@ -1,4 +1,5 @@ import { useNavigate } from 'react-router-dom'; +import { message } from '@toeverything/components/ui'; import { useSettingFlags, type SettingFlags } from './use-setting-flags'; import { copyToClipboard } from '@toeverything/utils'; import { @@ -91,7 +92,10 @@ export const useSettings = (): SettingItem[] => { { type: 'button', name: 'Copy Page Link', - onClick: () => copyToClipboard(window.location.href), + onClick: () => { + copyToClipboard(window.location.href); + message.success('Page link copied successfully'); + }, }, { type: 'separator', From 011aac0bfc68ae31636624a444a30e7783bc852b Mon Sep 17 00:00:00 2001 From: SaikaSakura Date: Thu, 11 Aug 2022 14:35:33 +0800 Subject: [PATCH 24/33] feat: fix-track-pad-scroll --- libs/components/editor-core/src/RenderRoot.tsx | 7 +++++++ libs/components/editor-core/src/Selection.tsx | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/libs/components/editor-core/src/RenderRoot.tsx b/libs/components/editor-core/src/RenderRoot.tsx index e4534b813..caa840cb3 100644 --- a/libs/components/editor-core/src/RenderRoot.tsx +++ b/libs/components/editor-core/src/RenderRoot.tsx @@ -102,6 +102,12 @@ export const RenderRoot: FC> = ({ editor.getHooks().onRootNodeMouseLeave(event); }; + const onContextmenu = ( + event: React.MouseEvent + ) => { + selectionRef.current?.onContextmenu(event); + }; + const onKeyDown: React.KeyboardEventHandler = event => { // IMP move into keyboard managers? editor.getHooks().onRootNodeKeyDown(event); @@ -165,6 +171,7 @@ export const RenderRoot: FC> = ({ onMouseUp={onMouseUp} onMouseLeave={onMouseLeave} onMouseOut={onMouseOut} + onContextMenu={onContextmenu} onKeyDown={onKeyDown} onKeyDownCapture={onKeyDownCapture} onKeyUp={onKeyUp} diff --git a/libs/components/editor-core/src/Selection.tsx b/libs/components/editor-core/src/Selection.tsx index e2dc34ed3..98ad9ccb1 100644 --- a/libs/components/editor-core/src/Selection.tsx +++ b/libs/components/editor-core/src/Selection.tsx @@ -29,6 +29,9 @@ export type SelectionRef = { onMouseDown: (event: React.MouseEvent) => void; onMouseMove: (event: React.MouseEvent) => void; onMouseUp: (event: React.MouseEvent) => void; + onContextmenu: ( + event: React.MouseEvent + ) => void; }; const getFixedPoint = ( @@ -207,10 +210,17 @@ export const SelectionRect = forwardRef( scrollManager.stopAutoScroll(); }; + const onContextmenu = () => { + if (mouseType.current === 'down') { + onMouseUp(); + } + }; + useImperativeHandle(ref, () => ({ onMouseDown, onMouseMove, onMouseUp, + onContextmenu, })); useEffect(() => { From 137d6a1923ee16ecb01f436facd230e76a708506 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 14:42:27 +0800 Subject: [PATCH 25/33] fix: hold pendant popover when completed incorrectly --- .../CreatePendantPanel.tsx | 20 +++++++++++- .../UpdatePendantPanel.tsx | 15 +++++++-- .../pendant-operation-panel/hooks.ts | 31 +------------------ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx index b09f23ed3..5a483adb5 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx @@ -1,5 +1,11 @@ import React, { useState, useEffect } from 'react'; -import { Input, Option, Select, Tooltip } from '@toeverything/components/ui'; +import { + Input, + message, + Option, + Select, + Tooltip, +} from '@toeverything/components/ui'; import { HelpCenterIcon } from '@toeverything/components/icons'; import { AsyncBlock } from '../../editor'; @@ -18,6 +24,7 @@ import { generateRandomFieldName, generateInitialOptions, getPendantConfigByType, + checkPendantForm, } from '../utils'; import { useOnCreateSure } from './hooks'; @@ -98,6 +105,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, diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx index 40ef97631..796ef39e0 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Input, Tooltip } from '@toeverything/components/ui'; +import { Input, message, Tooltip } from '@toeverything/components/ui'; import { HelpCenterIcon } from '@toeverything/components/icons'; import { PendantModifyPanel } from '../pendant-modify-panel'; import type { AsyncBlock } from '../../editor'; @@ -8,7 +8,7 @@ import { type RecastBlockValue, type RecastMetaProperty, } from '../../recast-block'; -import { getPendantConfigByType } from '../utils'; +import { checkPendantForm, getPendantConfigByType } from '../utils'; import { StyledPopoverWrapper, StyledOperationLabel, @@ -98,6 +98,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, diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts index 079cb2627..55016cc21 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts @@ -23,12 +23,7 @@ import { PendantTypes, type TempInformationType, } from '../types'; -import { - checkPendantForm, - getOfficialSelected, - getPendantConfigByType, -} from '../utils'; -import { message } from '@toeverything/components/ui'; +import { getOfficialSelected, getPendantConfigByType } from '../utils'; type SelectPropertyType = MultiSelectProperty | SelectProperty; type SureParams = { @@ -56,18 +51,6 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { newPropertyItem, newValue, }: SureParams) => { - const checkResult = checkPendantForm( - type, - fieldName, - newPropertyItem, - newValue - ); - - if (!checkResult.passed) { - await message.error(checkResult.message); - return; - } - if ( type === PendantTypes.MultiSelect || type === PendantTypes.Select || @@ -181,18 +164,6 @@ export const useOnUpdateSure = ({ newPropertyItem, newValue, }: SureParams) => { - const checkResult = checkPendantForm( - type, - fieldName, - newPropertyItem, - newValue - ); - - if (!checkResult.passed) { - await message.error(checkResult.message); - return; - } - if ( type === PendantTypes.MultiSelect || type === PendantTypes.Select || From b4724f3ae098135c6097d696ce556a6d581cfd75 Mon Sep 17 00:00:00 2001 From: JimmFly <102217452+JimmFly@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:48:51 +0800 Subject: [PATCH 26/33] fix: windows zoom (#190) Co-authored-by: JimmFly --- libs/components/board-state/src/tldraw-app.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libs/components/board-state/src/tldraw-app.ts b/libs/components/board-state/src/tldraw-app.ts index ab9492aab..05a7e8007 100644 --- a/libs/components/board-state/src/tldraw-app.ts +++ b/libs/components/board-state/src/tldraw-app.ts @@ -4075,7 +4075,12 @@ export class TldrawApp extends StateManager { 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); }; From 716c9ea34cae0314c2e03fcdc44c6087e48eb7c6 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 15:51:24 +0800 Subject: [PATCH 27/33] feat: update animate of pandent trigger line --- .../block-pendant/BlockPendantProvider.tsx | 36 ++++++++++++------- .../PendantHistoryPanel.tsx | 4 ++- .../pendant-popover/PendantPopover.tsx | 1 + 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx index 66cf4001a..48de13f4b 100644 --- a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx +++ b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx @@ -3,6 +3,7 @@ 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'; /** * @deprecated */ @@ -14,13 +15,16 @@ export const BlockPendantProvider: FC> = ({ block, children, }) => { + const triggerRef = useRef(); return ( {children} - - - + + + + + @@ -43,10 +47,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 +66,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', + paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`, + '&:hover': { + [`${StyledTriggerLine}`]: { + '&::before': { + opacity: '1', + }, + }, + }, +}); diff --git a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx index 0f610301d..ade7ced37 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-history-panel/PendantHistoryPanel.tsx @@ -29,6 +29,7 @@ export const PendantHistoryPanel = ({ const [history, setHistory] = useState([]); const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({}); + const historyPanelRef = useRef(); const { getValueHistory } = getRecastItemValue(block); useEffect(() => { @@ -84,7 +85,7 @@ export const PendantHistoryPanel = ({ }, [block, getProperties, groupBlock, recastBlock]); return ( - + {history.map(item => { const property = getProperty(item.id); return ( @@ -116,6 +117,7 @@ export const PendantHistoryPanel = ({ /> } trigger="click" + container={historyPanelRef.current} > { popoverHandlerRef.current?.setVisible(false); From 2d18e8f558fd0d1833b5ae9629a559f86a2e4bbe Mon Sep 17 00:00:00 2001 From: mitsuha Date: Thu, 11 Aug 2022 16:17:02 +0800 Subject: [PATCH 28/33] improvement: 1.left toolbar hover style#148; --- .../workspace/docs/collapsible-page-tree.tsx | 23 ++++------ .../pages/workspace/docs/workspace-name.tsx | 44 ++++++++++--------- .../src/lib/collapsible-title/index.tsx | 6 +-- .../EditorBoardSwitcher/StatusTrack.tsx | 1 - .../layout/src/header/LayoutHeader.tsx | 5 ++- .../activities/activities.tsx | 7 ++- .../workspace-sidebar/dot-icon/DotIcon.tsx | 9 ++++ .../src/workspace-sidebar/dot-icon/index.ts | 1 + .../workspace-sidebar/page-tree/DndTree.tsx | 2 +- .../workspace-sidebar/page-tree/PageTree.tsx | 6 +-- .../page-tree/tree-item/TreeItem.tsx | 18 ++++---- .../page-tree/tree-item/styles.ts | 13 +++--- 12 files changed, 72 insertions(+), 63 deletions(-) create mode 100644 libs/components/layout/src/workspace-sidebar/dot-icon/DotIcon.tsx create mode 100644 libs/components/layout/src/workspace-sidebar/dot-icon/index.ts diff --git a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx index 913f4024c..d37910583 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx @@ -8,6 +8,7 @@ import { usePageTree, } from '@toeverything/components/layout'; import { + IconButton, MuiBox as Box, MuiCollapse as Collapse, styled, @@ -27,6 +28,7 @@ const StyledBtn = styled('div')({ cursor: 'pointer', userSelect: 'none', flex: 1, + marginLeft: '12px', }); export type CollapsiblePageTreeProps = { @@ -70,7 +72,7 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) { display: 'flex', justifyContent: 'space-between', alignItems: 'center', - paddingRight: 1, + paddingRight: '12px', '&:hover': { background: '#f5f7f8', borderRadius: '5px', @@ -80,24 +82,17 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) { onMouseLeave={() => setNewPageBtnVisible(false)} > setOpen(prev => !prev)}> - {open ? ( - - ) : ( - - )} {title} {newPageBtnVisible && ( - + > + + )} {children ? ( diff --git a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx index 631149716..67a36e1ce 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx @@ -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'; @@ -124,24 +127,24 @@ export const WorkspaceName = () => { }; }, [currentSpaceId, fetchWorkspaceName]); - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - setInRename(false); - } - }, - [] - ); + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (e.key === 'Enter') { + e.stopPropagation(); + e.preventDefault(); + setInRename(false); + } + }, []); const handleChange = useCallback( - (e: React.ChangeEvent) => { - services.api.userConfig.setWorkspaceName( + async (e: ChangeEvent) => { + const name = e.target.value; + + await setWorkspaceName(name); + await services.api.userConfig.setWorkspaceName( currentSpaceId, - e.currentTarget.value + name ); }, - [] + [currentSpaceId] ); return ( @@ -165,7 +168,8 @@ export const WorkspaceName = () => { {inRename ? ( - setOpen(prev => !prev)}> - {open ? ( - - ) : ( - - )}
{ return { width: '64px', height: '32px', - backgroundColor: theme.affine.palette.textHover, border: '1px solid #ECF1FB', borderRadius: '8px', cursor: 'pointer', diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index 978b7f8df..85e715733 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -31,6 +31,7 @@ export const LayoutHeader = () => { size="large" hoverColor={'transparent'} disabled={true} + style={{ cursor: 'not-allowed' }} > @@ -124,11 +125,11 @@ const StyledHelper = styled('div')({ alignItems: 'center', }); -const StyledShare = styled(MuiButton)<{ disabled?: boolean }>({ +const StyledShare = styled('div')<{ disabled?: boolean }>({ padding: '10px 12px', fontWeight: 600, fontSize: '14px', - cursor: 'pointer', + cursor: 'not-allowed', color: '#98ACBD', textTransform: 'none', /* disabled for current time */ diff --git a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx index a532d9bd0..50c552c0a 100644 --- a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx +++ b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx @@ -10,9 +10,10 @@ import { } from '@toeverything/components/ui'; import { useNavigate } from 'react-router'; import { formatDistanceToNow } from 'date-fns'; +import { DotIcon } from '../dot-icon'; const StyledWrapper = styled('div')({ - paddingLeft: '12px', + width: '100%', span: { textOverflow: 'ellipsis', overflow: 'hidden', @@ -22,8 +23,8 @@ const StyledWrapper = styled('div')({ display: 'flex', alignItems: 'center', justifyContent: 'space-between', - paddingRight: '20px', whiteSpace: 'nowrap', + paddingLeft: '12px', '&:hover': { background: '#f5f7f8', borderRadius: '5px', @@ -106,6 +107,8 @@ export const Activities = () => { const { id, title, updated } = item; return ( + + { navigate(`/${currentSpaceId}/${id}`); diff --git a/libs/components/layout/src/workspace-sidebar/dot-icon/DotIcon.tsx b/libs/components/layout/src/workspace-sidebar/dot-icon/DotIcon.tsx new file mode 100644 index 000000000..4a86326d4 --- /dev/null +++ b/libs/components/layout/src/workspace-sidebar/dot-icon/DotIcon.tsx @@ -0,0 +1,9 @@ +import { PageInPageTreeIcon } from '@toeverything/components/icons'; + +export const DotIcon = () => { + return ( + + ); +}; diff --git a/libs/components/layout/src/workspace-sidebar/dot-icon/index.ts b/libs/components/layout/src/workspace-sidebar/dot-icon/index.ts new file mode 100644 index 000000000..5ee35a697 --- /dev/null +++ b/libs/components/layout/src/workspace-sidebar/dot-icon/index.ts @@ -0,0 +1 @@ +export { DotIcon } from './DotIcon'; diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx index 57d7643bb..40991b930 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx +++ b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx @@ -44,7 +44,7 @@ export type DndTreeProps = { */ export function DndTree(props: DndTreeProps) { const { - indentationWidth = 12, + indentationWidth = 20, collapsible, removable, showDragIndicator, diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/PageTree.tsx b/libs/components/layout/src/workspace-sidebar/page-tree/PageTree.tsx index d40442ce0..b2ea531e5 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/PageTree.tsx +++ b/libs/components/layout/src/workspace-sidebar/page-tree/PageTree.tsx @@ -3,10 +3,8 @@ import { DndTree } from './DndTree'; import { useDndTreeAutoUpdate } from './use-page-tree'; const Root = styled('div')({ - minWidth: 160, - maxWidth: 260, - marginLeft: 18, - marginRight: 6, + minWidth: '160px', + maxWidth: '276px', }); export const PageTree = () => { diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/TreeItem.tsx b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/TreeItem.tsx index 1a084d7b3..e2c0c7def 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/TreeItem.tsx +++ b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/TreeItem.tsx @@ -8,6 +8,7 @@ import { useParams } from 'react-router-dom'; import { useFlag } from '@toeverything/datasource/feature-flags'; import MoreActions from './MoreActions'; +import { DotIcon } from '../../dot-icon'; import { ActionButton, Counter, @@ -76,24 +77,25 @@ export const TreeItem = forwardRef( ghost={ghost} disableSelection={disableSelection} disableInteraction={disableInteraction} - spacing={`${indentationWidth * depth}px`} + spacing={`${indentationWidth * depth + 12}px`} + active={pageId === page_id} {...props} > - {childCount !== 0 && - (collapsed ? ( + {childCount !== 0 ? ( + collapsed ? ( ) : ( - ))} + ) + ) : ( + + )} - + {value} {BooleanPageTreeItemMoreActions && ( diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts index e05415ce8..0040e5bef 100644 --- a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts +++ b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/styles.ts @@ -15,11 +15,14 @@ export const Wrapper = styled('li')<{ indicator?: boolean; disableSelection?: boolean; disableInteraction?: boolean; + active?: boolean; }>` box-sizing: border-box; padding-left: ${({ spacing }) => spacing}; list-style: none; font-size: 14px; + background-color: ${({ active }) => (active ? '#f5f7f8' : 'transparent')}; + border-radius: 5px; ${({ clone, disableSelection }) => (clone || disableSelection) && @@ -126,8 +129,6 @@ export const ActionButton = styled('button')<{ fill?: string; }>` display: flex; - width: 12px; - padding: 0 15px; align-items: center; justify-content: center; flex: 0 0 auto; @@ -141,9 +142,10 @@ export const ActionButton = styled('button')<{ -webkit-tap-highlight-color: transparent; svg { + width: 20px; + height: 20px; flex: 0 0 auto; margin: auto; - height: 100%; overflow: visible; fill: #919eab; } @@ -182,8 +184,7 @@ export const TextLink = styled(Link, { appearance: none; text-decoration: none; user-select: none; - color: ${({ theme, active }) => - active ? theme.affine.palette.primary : 'unset'}; + color: #4c6275; `; export const TreeItemContent = styled('div')` @@ -195,7 +196,7 @@ export const TreeItemContent = styled('div')` align-items: center; justify-content: space-around; color: #4c6275; - padding-right: 0.5rem; + padding-right: 12px; overflow: hidden; &:hover { From 99355569472557dbfaa4e05c49ed02b986337e05 Mon Sep 17 00:00:00 2001 From: alt0 Date: Thu, 11 Aug 2022 17:40:50 +0800 Subject: [PATCH 29/33] fix: change edgeless tool selected color --- .../board-draw/src/components/tools-panel/ToolsPanel.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx b/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx index 48550054e..e8d10e1f7 100644 --- a/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx +++ b/libs/components/board-draw/src/components/tools-panel/ToolsPanel.tsx @@ -6,6 +6,7 @@ import { Tooltip, PopoverContainer, IconButton, + useTheme, } from '@toeverything/components/ui'; import { FrameIcon, @@ -71,6 +72,7 @@ export const ToolsPanel: FC<{ app: TldrawApp }> = ({ app }) => { const activeTool = app.useStore(activeToolSelector); const isToolLocked = app.useStore(toolLockedSelector); + const theme = useTheme(); return ( = ({ app }) => { style={{ color: activeTool === type - ? 'blue' + ? theme.affine.palette + .primary : '', }} onClick={() => { From 5e3f914182689d940bb6e0d4ea76c1696cb44e83 Mon Sep 17 00:00:00 2001 From: QiShaoXuan Date: Thu, 11 Aug 2022 18:52:31 +0800 Subject: [PATCH 30/33] fix: fix ui problems --- .../block-pendant/BlockPendantProvider.tsx | 22 +++++-- .../CreatePendantPanel.tsx | 2 +- .../UpdatePendantPanel.tsx | 2 +- libs/components/ui/src/select/Select.tsx | 64 +++++++++++-------- 4 files changed, 57 insertions(+), 33 deletions(-) diff --git a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx index 48de13f4b..031aa5e3a 100644 --- a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx +++ b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx @@ -4,6 +4,7 @@ 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 */ @@ -16,15 +17,26 @@ export const BlockPendantProvider: FC> = ({ children, }) => { const triggerRef = useRef(); + const { getProperties } = useRecastBlockMeta(); + const properties = getProperties(); + const { getValue } = getRecastItemValue(block); + const showTriggerLine = + properties.filter(property => getValue(property.id)).length === 0; + return ( {children} - - - - - + {showTriggerLine ? ( + + + + + + ) : null} diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx index 5a483adb5..6135d2b3c 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx @@ -81,7 +81,7 @@ export const CreatePendantPanel = ({ setFieldName(e.target.value); }} endAdornment={ - + diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx index 796ef39e0..2ff5515ba 100644 --- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/UpdatePendantPanel.tsx @@ -70,7 +70,7 @@ export const UpdatePendantPanel = ({ setFieldName(e.target.value); }} endAdornment={ - + diff --git a/libs/components/ui/src/select/Select.tsx b/libs/components/ui/src/select/Select.tsx index d1f3141e6..607c3bfa6 100644 --- a/libs/components/ui/src/select/Select.tsx +++ b/libs/components/ui/src/select/Select.tsx @@ -12,6 +12,7 @@ import SelectUnstyled, { } from '@mui/base/SelectUnstyled'; /* eslint-disable no-restricted-imports */ import PopperUnstyled from '@mui/base/PopperUnstyled'; +import { ArrowDropDownIcon } from '@toeverything/components/icons'; import { styled } from '../styled'; type ExtendSelectProps = { @@ -41,20 +42,29 @@ export const Select = forwardRef(function CustomSelect( const { width = '100%', style, listboxStyle, placeholder } = props; const components: SelectUnstyledProps['components'] = { // Root: generateStyledRoot({ width, ...style }), - Root: forwardRef((rootProps, rootRef) => ( - - {rootProps.children || ( - {placeholder} - )} - - )), + Root: forwardRef((rootProps, rootRef) => { + const { + ownerState: { open }, + } = rootProps; + + return ( + + {rootProps.children || ( + {placeholder} + )} + + + + + ); + }), Listbox: forwardRef((listboxProps, listboxRef) => ( ( RefAttributes ) => JSX.Element; +const StyledSelectedArrowWrapper = styled('div')<{ open: boolean }>( + ({ open }) => ({ + position: 'absolute', + top: '0', + bottom: '0', + right: '12px', + margin: 'auto', + lineHeight: '32px', + display: 'flex', + alignItems: 'center', + transform: `rotate(${open ? '180deg' : '0'})`, + }) +); + const StyledRoot = styled('div')(({ theme }) => ({ height: '32px', border: `1px solid ${theme.affine.palette.borderColor}`, @@ -95,18 +119,6 @@ const StyledRoot = styled('div')(({ theme }) => ({ [`&.${selectUnstyledClasses.expanded}`]: { borderColor: `${theme.affine.palette.primary}`, - '&::after': { - content: '"β–΄"', - }, - }, - '&::after': { - content: '"β–Ύ"', - position: ' absolute', - top: '0', - bottom: '0', - right: '12px', - margin: 'auto', - lineHeight: '32px', }, })); From 11c7e3ad83b49eb2944d1864e51d9be678982164 Mon Sep 17 00:00:00 2001 From: austaras Date: Thu, 11 Aug 2022 19:00:52 +0800 Subject: [PATCH 31/33] fix(editor): gap between block --- libs/components/account/src/login/fs.tsx | 1 + .../editor-blocks/src/blocks/group/GroupView.tsx | 4 +++- .../src/components/BlockContainer/BlockContainer.tsx | 1 + .../editor-core/src/block-pendant/BlockPendantProvider.tsx | 4 ++-- .../src/menu/left-menu/LeftMenuDraggable.tsx | 7 ++++--- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/libs/components/account/src/login/fs.tsx b/libs/components/account/src/login/fs.tsx index 99f1252db..07d6465ba 100644 --- a/libs/components/account/src/login/fs.tsx +++ b/libs/components/account/src/login/fs.tsx @@ -76,6 +76,7 @@ export const FileSystem = () => { onSelected(); } catch (e) { setError(true); + onSelected(); setTimeout(() => setError(false), 3000); } }} diff --git a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx index 9b0f0f910..b990d1aa5 100644 --- a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx +++ b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx @@ -1,6 +1,8 @@ import { addNewGroup, + LINE_GAP, RecastScene, + TAG_GAP, useCurrentView, useOnSelect, } from '@toeverything/components/editor-core'; @@ -61,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 ? { diff --git a/libs/components/editor-blocks/src/components/BlockContainer/BlockContainer.tsx b/libs/components/editor-blocks/src/components/BlockContainer/BlockContainer.tsx index f03cd9ffe..dbe12277b 100644 --- a/libs/components/editor-blocks/src/components/BlockContainer/BlockContainer.tsx +++ b/libs/components/editor-blocks/src/components/BlockContainer/BlockContainer.tsx @@ -27,5 +27,6 @@ export const BlockContainer: FC = function ({ export const Container = styled('div')<{ selected: boolean }>( ({ selected, theme }) => ({ backgroundColor: selected ? theme.affine.palette.textSelected : '', + marginBottom: '2px', }) ); diff --git a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx index 66cf4001a..71b28ead6 100644 --- a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx +++ b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx @@ -28,7 +28,7 @@ export const BlockPendantProvider: FC> = ({ }; export const LINE_GAP = 16; -const TAG_GAP = 4; +export const TAG_GAP = 4; const StyledTriggerLine = styled('div')({ padding: `${TAG_GAP}px 0`, @@ -63,7 +63,7 @@ const StyledTriggerLine = styled('div')({ const Container = styled('div')({ position: 'relative', - paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`, + padding: `${TAG_GAP * 2}px 0 ${LINE_GAP - TAG_GAP * 4}px 0`, '&:hover': { [StyledTriggerLine.toString()]: { '&::before': { diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx index 4f10f16fb..750490650 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx @@ -15,6 +15,7 @@ import { BlockDropPlacement, LINE_GAP, AsyncBlock, + TAG_GAP, } from '@toeverything/framework/virgo'; import { Button } from '@toeverything/components/common'; import { styled } from '@toeverything/components/ui'; @@ -78,13 +79,13 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) { }; const bottomLineStyle = { ...horizontalLineStyle, - top: intersectionRect.bottom + 1 - rootRect.y - LINE_GAP, + top: intersectionRect.bottom + 1 - rootRect.y - LINE_GAP + TAG_GAP, }; const verticalLineStyle = { ...lineStyle, width: 2, - height: intersectionRect.height - LINE_GAP, + height: intersectionRect.height - LINE_GAP + TAG_GAP, top: intersectionRect.y - rootRect.y, }; const leftLineStyle = { @@ -228,7 +229,7 @@ export const LeftMenuDraggable: FC = props => { MENU_WIDTH - MENU_BUTTON_OFFSET - rootRect.left, - top: block.rect.top - rootRect.top, + top: block.rect.top - rootRect.top + TAG_GAP * 2, opacity: visible ? 1 : 0, zIndex: 1, }} From 5a23f67d312ecb329b50b2d47effe0d6562b3ba9 Mon Sep 17 00:00:00 2001 From: austaras Date: Thu, 11 Aug 2022 16:33:03 +0800 Subject: [PATCH 32/33] feat(whiteboard): cursor style when dragging --- libs/components/board-draw/src/TlDraw.tsx | 53 +++++++++++-------- libs/components/board-state/src/tldraw-app.ts | 41 ++++++++------ .../src/hand-draw/hand-draw-tool.ts | 39 +++++--------- libs/components/board-types/src/types.ts | 1 + .../src/menu/left-menu/LeftMenuPlugin.tsx | 2 +- 5 files changed, 72 insertions(+), 64 deletions(-) diff --git a/libs/components/board-draw/src/TlDraw.tsx b/libs/components/board-draw/src/TlDraw.tsx index 21044ad51..1e95506ab 100644 --- a/libs/components/board-draw/src/TlDraw.tsx +++ b/libs/components/board-draw/src/TlDraw.tsx @@ -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(null); + const rWrapper = useRef(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 ( @@ -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; + focusableRef: RefObject; }) { 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', diff --git a/libs/components/board-state/src/tldraw-app.ts b/libs/components/board-state/src/tldraw-app.ts index 05a7e8007..b062cc8b8 100644 --- a/libs/components/board-state/src/tldraw-app.ts +++ b/libs/components/board-state/src/tldraw-app.ts @@ -219,8 +219,6 @@ export class TldrawApp extends StateManager { isPointing = false; - isForcePanning = false; - editingStartTime = -1; fileSystemHandle: FileSystemHandle | null = null; @@ -262,7 +260,7 @@ export class TldrawApp extends StateManager { 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 { ); 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 { 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 { break; } case ' ': { - this.isForcePanning = true; + this.patchState({ + settings: { + forcePanning: true, + }, + }); this.spaceKey = true; break; } @@ -3976,7 +3978,12 @@ export class TldrawApp extends StateManager { break; } case ' ': { - this.isForcePanning = false; + this.patchState({ + settings: { + forcePanning: + this.currentTool.type === TDShapeType.HandDraw, + }, + }); this.spaceKey = false; break; } @@ -4069,7 +4076,7 @@ export class TldrawApp extends StateManager { 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); }; @@ -4098,7 +4105,7 @@ export class TldrawApp extends StateManager { 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 @@ -4122,20 +4129,23 @@ export class TldrawApp extends StateManager { 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); }; @@ -4522,7 +4532,7 @@ export class TldrawApp extends StateManager { assets: {}, }; - static default_state: TDSnapshot = { + static defaultState: TDSnapshot = { settings: { isCadSelectMode: false, isPenMode: false, @@ -4532,6 +4542,7 @@ export class TldrawApp extends StateManager { isSnapping: false, isDebugMode: false, isReadonlyMode: false, + forcePanning: false, keepStyleMenuOpen: false, nudgeDistanceLarge: 16, nudgeDistanceSmall: 1, diff --git a/libs/components/board-tools/src/hand-draw/hand-draw-tool.ts b/libs/components/board-tools/src/hand-draw/hand-draw-tool.ts index c579c8e7e..180b93644 100644 --- a/libs/components/board-tools/src/hand-draw/hand-draw-tool.ts +++ b/libs/components/board-tools/src/hand-draw/hand-draw-tool.ts @@ -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, + }, + }); }; } diff --git a/libs/components/board-types/src/types.ts b/libs/components/board-types/src/types.ts index fbf3e5c30..6f80a965b 100644 --- a/libs/components/board-types/src/types.ts +++ b/libs/components/board-types/src/types.ts @@ -84,6 +84,7 @@ export interface TDSnapshot { isPenMode: boolean; isReadonlyMode: boolean; isZoomSnap: boolean; + forcePanning: boolean; keepStyleMenuOpen: boolean; nudgeDistanceSmall: number; nudgeDistanceLarge: number; diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx index 5281c21b0..4eda43b55 100644 --- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx +++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx @@ -10,7 +10,7 @@ import { import { PluginRenderRoot } from '../../utils'; import { Subject, throttleTime } from 'rxjs'; import { domToRect, last, Point } from '@toeverything/utils'; -const DRAG_THROTTLE_DELAY = 150; +const DRAG_THROTTLE_DELAY = 60; export class LeftMenuPlugin extends BasePlugin { private _mousedown?: boolean; private _root?: PluginRenderRoot; From d7a41cd68d7d390faca1e2ff7c549dda83c2fdc7 Mon Sep 17 00:00:00 2001 From: lawvs <18554747+lawvs@users.noreply.github.com> Date: Thu, 11 Aug 2022 18:57:27 +0800 Subject: [PATCH 33/33] chroe: update contributors --- .all-contributorsrc | 115 ++++++++++++++++++++++++++++++++++++++++++-- README.md | 40 ++++++++++----- 2 files changed, 140 insertions(+), 15 deletions(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 3286d1be2..9f85871a6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -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" + ] } ] } diff --git a/README.md b/README.md index 8076877f0..9e2ebc85f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066 --> -[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 @@ -171,19 +171,35 @@ For help, discussion about best practices, or any other conversation that would - - - - - - - + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + +

DarkSky

πŸ’» πŸ“–

Chi Zhang

πŸ’» πŸ“–

wang xinglong

πŸ’» πŸ“–

DiamondThree

πŸ’» πŸ“–

Whitewater

πŸ’» πŸ“–

xiaodong zuo

πŸ’» πŸ“–

MingLIang Wang

πŸ’» πŸ“–

DarkSky

πŸ’» πŸ“–

Chi Zhang

πŸ’» πŸ“–

wang xinglong

πŸ’» πŸ“–

DiamondThree

πŸ’» πŸ“–

Whitewater

πŸ’» πŸ“–

xiaodong zuo

πŸ’» πŸ“–

MingLIang Wang

πŸ’» πŸ“–

Qi

πŸ’» πŸ“–

mitsuhatu

πŸ’» πŸ“–

Austaras

πŸ’» πŸ“–

Jin Yao

πŸ’» πŸ“–

Qi

πŸ’» πŸ“–

mitsuhatu

πŸ’» πŸ“–

Austaras

πŸ’» πŸ“–

Jin Yao

πŸ’» πŸ“–

HeJiachen-PM

πŸ“–

Yipei Wei

πŸ“–

fanjing22

🎨

Svaney

🎨

Guozhu Liu

🎨

fyZheng07

πŸ“‹ πŸ““

CJSS

πŸ“–

Carlos Rafael

πŸ’»

Caleb OLeary

πŸ’»

JimmFly

πŸ’»

Weston Graham

πŸ“–

pointmax

πŸ“–