diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..ba592b3312 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,46 @@ +name: 🐛 Bug report +description: Report a reproducible bug or regression +title: "[bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this bug report! + - type: input + id: description + attributes: + label: Describe the bug + placeholder: A clear and concise description of what the bug is. + - type: textarea + id: reproduce + attributes: + label: To Reproduce + placeholder: "Steps to reproduce the behavior\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error" + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + placeholder: If applicable, add screenshots to help explain your problem. + - type: textarea + id: expected + attributes: + label: Expected behavior + placeholder: A clear and concise description of what you expected to happen. + - type: input + id: platform + attributes: + label: Platform + placeholder: e.g. MacOS, Windows10... + - type: input + id: browser + attributes: + label: Browser + placeholder: e.g. Chrome, Safari + - type: textarea + id: additional + attributes: + label: Additional context + placeholder: Add any other context about the problem here. + diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..bad3192b20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: false +contact_links: + - name: 💭 Questions and Help - Reddit + url: https://www.reddit.com/r/Affine/ + about: Please ask and answer questions here. + - name: 💬 Questions and Help - Telegram + url: https://t.me/affineworkos + about: Please ask and answer questions here. + - name: 🗯 Questions and Help - Discord + url: https://discord.gg/yz6tGVsf5p + about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000..54c1caa4f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,31 @@ +name: ✨ Feature request +description: An idea or request for new functionality +title: "[feature]: " +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this feature request! + - type: textarea + id: description + attributes: + label: 1~3 main use cases of the proposed feature + description: e.g. As a ..., I have many tasks scattered across documents, and I want to have a unified entry to view these tasks. + placeholder: e.g. As a ... + validations: + required: true + - type: textarea + id: solution + attributes: + label: Ideas for solution + placeholder: e.g. A task view can be added to view all tasks. + - type: input + id: userType + attributes: + label: what types of users can benefit from using your proposed feature + placeholder: busy student + - type: textarea + id: additional + attributes: + label: Additional context + placeholder: Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/improvement-request.yml b/.github/ISSUE_TEMPLATE/improvement-request.yml new file mode 100644 index 0000000000..769e679131 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/improvement-request.yml @@ -0,0 +1,31 @@ +name: 🪄 Improvement request +description: An improvement to existing functionality +title: "[improvement]: " +labels: ["improvement"] +body: + - type: markdown + attributes: + value: Thanks for taking the time to fill out this improvement request! + - type: textarea + id: description + attributes: + label: 1~3 main use cases of the proposed improvement + description: e.g. As a ..., I have many tasks scattered across documents, and I want to have a unified entry to view these tasks. + placeholder: e.g. As a ... + validations: + required: true + - type: textarea + id: solution + attributes: + label: Ideas for solution + placeholder: e.g. A task view can be added to view all tasks. + - type: input + id: userType + attributes: + label: what types of users can benefit from using your proposed improvement + placeholder: busy student + - type: textarea + id: additional + attributes: + label: Additional context + placeholder: Add any other context or screenshots about the improvement request here. diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml new file mode 100644 index 0000000000..59bf758cdc --- /dev/null +++ b/.github/workflows/venus.yml @@ -0,0 +1,66 @@ +name: Build Venus + +on: + push: + branches: [master] + paths: + - 'apps/venus/**' + - '.github/workflows/venus.yml' + pull_request: + branches: [master] + paths: + - 'apps/venus/**' + - '.github/workflows/venus.yml' + +# Cancels all previous workflow runs for pull requests that have not completed. +# See https://docs.github.com/en/actions/using-jobs/using-concurrency +concurrency: + # The concurrency group contains the workflow name and the branch name for + # pull requests or the commit hash for any other events. + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.head_ref || github.sha }} + cancel-in-progress: true + +env: + REGISTRY: ghcr.io + NAMESPACE: toeverything + VENUS_IMAGE_NAME: venus + IMAGE_TAG: canary-${{ github.sha }} + IMAGE_TAG_LATEST: nightly-latest + +jobs: + ligo-virgo: + runs-on: self-hosted + environment: development + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker (venus) + id: meta_venus + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.VENUS_IMAGE_NAME }} + tags: | + ${{ env.IMAGE_TAG }} + ${{ env.IMAGE_TAG_LATEST }} + + - name: Build and push Docker image (venus) + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + file: ./Dockerfile-venus + push: ${{ github.ref == 'refs/heads/master' && true || false }} + tags: ${{ steps.meta_venus.outputs.tags }} + labels: ${{ steps.meta_venus.outputs.labels }} + target: venus diff --git a/Caddyfile-venus b/Caddyfile-venus new file mode 100644 index 0000000000..7c05ada7a9 --- /dev/null +++ b/Caddyfile-venus @@ -0,0 +1,28 @@ +:80 { + root /* ./dist + + file_server { + precompressed br + } + + encode { + zstd + gzip 9 + } + + @notStatic { + not path /*.css + not path /*.js + not path /*.png + not path /*.jpg + not path /*.svg + not path /*.ttf + not path /*.eot + not path /*.woff + not path /*.woff2 + } + + handle @notStatic { + try_files {path} /index.html + } +} diff --git a/Dockerfile-venus b/Dockerfile-venus new file mode 100644 index 0000000000..55ece303c6 --- /dev/null +++ b/Dockerfile-venus @@ -0,0 +1,21 @@ +FROM node:16-alpine as builder +WORKDIR /app +COPY . . +RUN apk add g++ make python3 git +RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus + +FROM node:16-alpine as relocate +WORKDIR /app +COPY --from=builder /app/dist/apps/venus ./dist +COPY --from=builder /app/Caddyfile-venus ./Caddyfile +RUN rm ./dist/*.txt + +# ============= +# venus image +# ============= +FROM caddy:2.4.6-alpine as venus +WORKDIR /app +COPY --from=relocate /app . + +EXPOSE 80 +CMD ["caddy", "run"] \ No newline at end of file diff --git a/README.md b/README.md index a7b35961f4..0fbcf5bf80 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,150 @@ -# AFFiNE +

+ + AFFiNE.PRO
+
+ The Next-Gen Knowledge Base to Replace Notion & Miro. +
+

+

+Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Free to use. +

+ +
+ + -[![All Contributors](https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square)](#contributors-) +[all-contributors-badge]: https://img.shields.io/badge/all_contributors-11-orange.svg?style=flat-square -Workspace for AFFiNE +[![All Contributors][all-contributors-badge]](#contributors) +[![Node](https://img.shields.io/badge/node->=16.0-success)](https://www.typescriptlang.org/) +[![React](https://img.shields.io/badge/TypeScript-4.7-3178c6)](https://www.typescriptlang.org/) +[![React](https://img.shields.io/badge/React-18-61dafb)](https://reactjs.org/) +[![Rust](https://img.shields.io/badge/Rust-1.62-dea584)](https://www.rust-lang.org/) -## Installation +
-```sh -# Clone the repo -git clone git@github.com:toeverything/AFFiNE.git -``` +

+ Website • + Discord • + Twitter • + Medium • + Telegram +

-Once cloned, switch to the master branch and navigate to the folder by typing `cd AFFiNE` and then running the following commands: +

affine_screen

-```sh -# Install all project dependencies -npm i -g pnpm -pnpm i +# Stay Up-to-Date -# Start the project -pnpm start -open http://localhost:4200/ -``` +![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif) -This project uses pnpm for package management and is built based on nx. It is recommended to install the [nx console](https://marketplace.visualstudio.com/items?itemName=nrwl.angular-console) plugin to create dependencies +# Table of contents -**If it is development, you can add environment variables in the project directory .env.local file** +- [Stay Up-to-Date](#stay-up-to-date) +- [Table of contents](#table-of-contents) + - [Shape your page](#shape-your-page) + - [Plan your task](#plan-your-task) + - [Sort your knowledge](#sort-your-knowledge) + - [Create your story](#create-your-story) +- [Getting Started with development](#getting-started-with-development) +- [Roadmap](#roadmap) +- [Releases](#releases) +- [Feature requests](#feature-requests) +- [FAQ](#faq) +- [The Philosophy of AFFiNE](#the-philosophy-of-affine) +- [Community](#community) +- [Contributors](#contributors) +- [License](#license) -``` -NODE_ENV=development -``` +## Shape your page -## Scripts +![546163d6-4c39-4128-ae7f-55d59bc3b76b](https://user-images.githubusercontent.com/79301703/182365611-b0ba3690-21c0-4d9b-bfbc-0bc15da05aeb.gif) -1. Create react dependency library: `pnpm run add:library` -2. Create react components: `pnpm run add:components` -3. Create a data source: `pnpm run add:datasource` -4. Unit testing: `pnpm test` -5. Compile specific components - - `pnpm build/test/lint `project name - - Project name reference workspace.json -6. Create react/node program: use nx console -7. If you need to use the git cz function, please install it globally first commitizen `npm install -g commitizen conventional-changelog conventional-changelog-cli` +## Plan your task -## Contributing +![41a7b3a4-32f2-4d18-ac6b-57d1e1fda753](https://user-images.githubusercontent.com/79301703/182366553-1f6558a7-f17b-4611-ab95-aea3ec997154.gif) -- Generic functional components (such as ui components) are placed in `libs/components/common` - - components within common are not allowed to reference _components_ except utils and dependencies - - Common components can reference each other -- Business components are placed in `libs/components` -- The data source component is placed in `libs/datasource` - api request code, schema, etc. belong to the data source - Please see [CONTRIBUTING](/docs/CONTRIBUTING.md) +## Sort your knowledge -## Documentation +![c9e1ff46-cec2-411b-b89d-6727a5e6f6c3](https://user-images.githubusercontent.com/79301703/182366602-08e44d28-a031-4097-9904-52fb9b1e9e17.gif) -- [how-to-write-css-in-affine.md](/docs/how-to-write-css-in-affine.md) -- [how-to-add-ui-component-in-affine.md](/docs/how-to-add-ui-component-in-affine.md) -- [how-to-customize-rollup-config.md](docs/how-to-customize-rollup-config.md) -- [how-to-auto-download-figma-assets-in-affine.md](docs/how-to-auto-download-figma-assets-in-affine.md) -- [affine-icons-user-guide.md](docs/affine-icons-user-guide.md) +## Create your story -## Community +We want your data always to be yours, and we don't want to make any sacrifice to your accessibility. Your data is always local-stored first, yet we support real-time collaboration on a peer-to-peer basis. We don't think "privacy-first" is a good excuse for not supporting modern web features. +Collaboration isn't only necessary for teams -- you may take and insert pics on your phone, then edit them on your desktop, and share them with your collaborators. +Affine is fully built with web technologies so that consistency and accessibility are always guaranteed on Mac, Windows and Linux. The local file system support will be available when version 0.0.1beta is released. + +# Getting Started with development + +Please view the [documentation](https://affine.gitbook.io/affine/) in Contribute-to-AFFiNE/Software-Contributions/Environment-setup. + +# Roadmap + +Coming Soon... + +# Releases + +Get our latest [release notes](https://github.com/toeverything/AFFiNE/wiki) from here. + +# Feature requests + +Please go to [Feature request](https://github.com/toeverything/AFFiNE/issues). + +# FAQ + +Get quick help on [Telegram](https://t.me/affineworkos) and [Discord](https://discord.gg/yz6tGVsf5p) along with other developers and contributors. + +Latest news and technology sharing on [Twitter](https://twitter.com/AffineOfficial), [Medium](https://medium.com/@affineworkos) and [AFFiNE Blog](https://blog.affine.pro/). + +# The Philosophy of AFFiNE + +Timothy Berners-Lee once taught us about the idea of the semantic web, where all the data can be interpreted in any form while the "truth" is kept. This gives our best image of an ideal knowledge base by far, that sorting of information, planning of project and goals as well as creating of knowledge can be all together. +We have witnessed waves of paradigm shift so many times. At first, everything was noted on office-like apps or DSL like LaTeX, then we found todo-list apps and WYSIWYG markdown editors better for writing and planning. Finally, here comes Notion and Miro, who take advantage of the idea of blocks to further liberate our creativity. +It is all perfect... If there are not so many waste operations and redundant information. And, we insist that privacy first should always be given by default. +That's why we are making AFFiNE. Some of the most important features are: + +- Transformable + - Every block can be transformed equally as a database + - e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view. + - Every doc can be turned into a whiteboard + - An always good-to-read, structured docs-form page is the best for your notes, but a boundless doodle surface is better for collaboration and creativity. +- Atomic + - The basic element of affine are blocks, not pages. + - Blocks can be directly reuse and synced between pages. + - Pages and blocks are searched and organized on the basis of connected graphs, not tree-like paths. + - Dual-link and semantic search are fully supported. +- Collaborative and privacy-first + - Data is always stored locally by default + - CRDTs are applied so that peer-to-peer collaboration is possible. + +We really appreciate the idea of Monday, airtable and notion database. They inspired what we think is right for task management. But we don't like the repeated works -- we don't want to set a todo easily with markdown but end up re-write it again in kanban or other databases. +With AFFiNE, every block group has infinite views, for you to keep your single source of truth. + +We would like to give special thanks to the innovators and pioneers who greatly inspired us: + +- Quip & Notion -- that docs can be organized as blocks +- Taskade & Monday -- brillant multi-demensional tables +- Height & Linear -- beautiful task management tool + +We would also like to give thanks to open-source projects that make affine possible: + +- Yjs & Yrs +- React +- Rust + +# Community For help, discussion about best practices, or any other conversation that would benefit from being searchable: [Discuss AFFiNE on GitHub](https://github.com/toeverything/AFFiNE/discussions) -## Contributors +# Contributors @@ -98,7 +172,7 @@ For help, discussion about best practices, or any other conversation that would -## License +# License AFFiNE is distributed under the terms of MIT license. diff --git a/apps/ligo-virgo/src/index.tsx b/apps/ligo-virgo/src/index.tsx index 03e5d1e5ab..14e33ec10f 100644 --- a/apps/ligo-virgo/src/index.tsx +++ b/apps/ligo-virgo/src/index.tsx @@ -1,5 +1,4 @@ /* eslint-disable filename-rules/match */ -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx index 5aceacb68c..b2ebc5f7dc 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx @@ -1,5 +1,11 @@ /* eslint-disable filename-rules/match */ -import { useEffect, useRef, type UIEvent, useState } from 'react'; +import { + useEffect, + useRef, + type UIEvent, + useState, + useLayoutEffect, +} from 'react'; import { useParams } from 'react-router'; import { MuiBox as Box, @@ -17,6 +23,7 @@ import { CollapsibleTitle } from '@toeverything/components/common'; import { useShowSpaceSidebar, useUserAndSpaces, + usePageClientWidth, } from '@toeverything/datasource/state'; import { services } from '@toeverything/datasource/db-service'; @@ -47,11 +54,11 @@ export function Page(props: PageProps) { } ); - await services.api.userConfig.addRecentPage( - props.workspace, - user.id, - page_id - ); + // await services.api.userConfig.addRecentPage( + // props.workspace, + // user.id, + // page_id + // ); await services.api.editorBlock.clearUndoRedo(props.workspace); }; updateRecentPages(); @@ -113,7 +120,7 @@ const EditorContainer = ({ workspace: string; }) => { const [lockScroll, setLockScroll] = useState(false); - const scrollContainerRef = useRef(); + const scrollContainerRef = useRef(); const editorRef = useRef(); const onScroll = (event: UIEvent) => { editorRef.current.getHooks().onRootNodeScroll(event); @@ -130,6 +137,17 @@ const EditorContainer = ({ }; }, []); + const { setPageClientWidth } = usePageClientWidth(); + useEffect(() => { + if (scrollContainerRef.current) { + const obv = new ResizeObserver(e => { + setPageClientWidth(e[0].contentRect.width); + }); + obv.observe(scrollContainerRef.current); + return () => obv.disconnect(); + } + }); + return ( { const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar(); const [inRename, setInRename] = useState(false); const [workspaceName, setWorkspaceName] = useState(''); + const [workspaceId, setWorkspaceId] = useState(''); const fetchWorkspaceName = useCallback(async () => { if (!currentSpaceId) { @@ -88,6 +89,11 @@ export const WorkspaceName = () => { currentSpaceId ); setWorkspaceName(name); + + const workspaceId = await services.api.userConfig.getWorkspaceId( + currentSpaceId + ); + setWorkspaceId(workspaceId); }, [currentSpaceId]); useEffect(() => { @@ -150,7 +156,7 @@ export const WorkspaceName = () => { ) : ( setInRename(true)}> - {workspaceName} + {workspaceName || workspaceId} )} diff --git a/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx b/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx deleted file mode 100644 index fed9714482..0000000000 --- a/apps/ligo-virgo/src/pages/workspace/whiteboard/index.bak.tsx +++ /dev/null @@ -1,53 +0,0 @@ -// import { FC, useMemo, useState } from 'react'; -// import { useParams } from 'react-router-dom'; -// import { TDPage } from '@toeverything/framework/whiteboard'; -// import { -// AffineWhiteboard, -// WhiteboardMeta, -// AffineEditorShape -// } from '@toeverything/components/affine-whiteboard'; - -// interface EditorShapeProps { -// blockIds: string | string[]; -// point: [number, number]; -// } - -// const createEditorShape = (props: EditorShapeProps): AffineEditorShape => { -// const block_ids = Array.isArray(props.blockIds) -// ? props.blockIds -// : [props.blockIds]; -// return { -// id: block_ids.join('_'), -// label: '', -// childIndex: 1, -// name: 'affine_editor', -// parentId: 'page', -// point: props.point, -// rotation: 0, -// size: [400, 200], -// style: { -// color: 'black', -// size: 'small', -// isFilled: false, -// dash: 'draw', -// scale: 1 -// } as any, -// type: 'affineEditor', -// blockIds: block_ids -// }; -// }; - -// const Whiteboard: FC = () => { -// const { workspace_id, page_id } = useParams(); -// const [shapes, set_shapes] = useState({}); -// const meta = useMemo(() => { -// return { -// workspace: workspace_id, -// rootBlockId: page_id -// }; -// }, [workspace_id, page_id]); - -// return page_id ? : null; -// }; - -// export default Whiteboard; diff --git a/apps/venus/.babelrc b/apps/venus/.babelrc new file mode 100644 index 0000000000..f0aa1cb969 --- /dev/null +++ b/apps/venus/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/venus/.browserslistrc b/apps/venus/.browserslistrc new file mode 100644 index 0000000000..f1d12df4fa --- /dev/null +++ b/apps/venus/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. \ No newline at end of file diff --git a/apps/venus/.eslintrc.json b/apps/venus/.eslintrc.json new file mode 100644 index 0000000000..1f5ec76fbb --- /dev/null +++ b/apps/venus/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/venus/package.json b/apps/venus/package.json new file mode 100644 index 0000000000..20c7374fdc --- /dev/null +++ b/apps/venus/package.json @@ -0,0 +1,19 @@ +{ + "name": "venus", + "version": "1.0.0", + "license": "MIT", + "description": "", + "scripts": {}, + "keywords": [], + "author": "DarkSky ", + "dependencies": { + "@mui/joy": "^5.0.0-alpha.39", + "@emotion/react": "^11.10.0", + "@emotion/styled": "^11.10.0", + "lozad": "^1.16.0" + }, + "devDependencies": { + "mini-css-extract-plugin": "^2.6.1", + "webpack": "^5.73.0" + } +} diff --git a/apps/venus/project.json b/apps/venus/project.json new file mode 100644 index 0000000000..a3a84c9405 --- /dev/null +++ b/apps/venus/project.json @@ -0,0 +1,76 @@ +{ + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/venus/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/web:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/venus", + "index": "apps/venus/src/index.html", + "baseHref": "/", + "main": "apps/venus/src/index.tsx", + "polyfills": "apps/venus/src/polyfills.ts", + "tsConfig": "apps/venus/tsconfig.app.json", + "assets": ["apps/venus/src/assets"], + "styles": [], + "scripts": [], + "webpackConfig": "apps/venus/webpack.config.js" + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/venus/src/environments/environment.ts", + "with": "apps/venus/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": true, + "extractLicenses": false, + "vendorChunk": false, + "generateIndexHtml": false + } + } + }, + "serve": { + "executor": "@nrwl/web:dev-server", + "defaultConfiguration": "development", + "options": { + "buildTarget": "venus:build", + "hmr": true, + "open": true + }, + "configurations": { + "development": { + "buildTarget": "venus:build:development" + }, + "production": { + "buildTarget": "venus:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/venus/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/venus"], + "options": { + "jestConfig": "apps/venus/jest.config.ts", + "passWithNoTests": true + } + } + }, + "tags": ["app:venus"] +} diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx new file mode 100644 index 0000000000..8e17108f13 --- /dev/null +++ b/apps/venus/src/app/index.tsx @@ -0,0 +1,938 @@ +/* eslint-disable max-lines */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable filename-rules/match */ +import { useEffect, useMemo, useState } from 'react'; +import clsx from 'clsx'; + +import { CssVarsProvider, styled } from '@mui/joy/styles'; +import { Box, Button, Container, Grid, SvgIcon, Typography } from '@mui/joy'; +import Card from '@mui/joy/Card'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import RedditIcon from '@mui/icons-material/Reddit'; +import TelegramIcon from '@mui/icons-material/Telegram'; +// eslint-disable-next-line no-restricted-imports +import { useMediaQuery } from '@mui/material'; + +const DiscordIcon = (props: any) => { + return ( + + + + + + + + + + + ); +}; + +const VenusContainer = styled(Container)({ + margin: '1em auto', +}); + +const Alternatives = styled(Box)<{ width: string }>(({ width }) => ({ + position: 'relative', + width: '24em', + height: '128px', + transform: 'translateY(-8px)', + overflowY: 'hidden', + '@media (max-width: 768px)': { + width, + height: '48px', + transform: 'translateY(0)', + }, + '& .scroll-element': { + width: 'inherit', + height: 'inherit', + position: 'absolute', + left: '0%', + top: '0%', + lineHeight: '96px', + '@media (max-width: 768px)': { + lineHeight: '32px', + }, + }, + '& .scroll-element.active': { + animation: 'primary 500ms linear infinite', + }, + '.primary.active': { + animation: 'primary 500ms linear infinite', + }, + '.secondary.active': { + animation: 'secondary 500ms linear infinite', + }, + '@keyframes primary': { + from: { + top: '0%', + }, + to: { + top: '-100%', + }, + }, + '@keyframes secondary': { + from: { + top: '100%', + }, + to: { + top: '0%', + }, + }, +})); + +const _alternatives = ['Notion', 'Miro', 'Monday']; +const _alternativesSize = [8, 6, 10]; + +const Product = () => { + const [idx, setIdx] = useState(0); + const [last, current] = useMemo( + () => [ + _alternatives[idx], + _alternatives[idx + 1] ? _alternatives[idx + 1] : _alternatives[0], + ], + [idx] + ); + const maxWidth = useMemo(() => _alternativesSize[idx], [idx]); + const [active, setActive] = useState(false); + const matches = useMediaQuery('(max-width: 768px)'); + + useEffect(() => { + const handle = setInterval(() => { + setActive(true); + setTimeout( + () => { + setIdx(idx => (_alternatives[idx + 1] ? idx + 1 : 0)); + setActive(false); + }, + matches ? 450 : 380 + ); + }, 2000); + return () => clearInterval(handle); + }, [matches]); + + return ( + + + + {last} + + + + + {current} + + + + ); +}; + +const AffineImage = styled('img')({ + maxWidth: '100%', + objectFit: 'contain', +}); + +const GitHub = (props: { center?: boolean; flat?: boolean }) => { + const matches = useMediaQuery('(max-width: 768px)'); + + return ( + + ); +}; + +export function App() { + const matches = useMediaQuery('(max-width: 768px)'); + + return ( + + div': { + marginTop: '1em', + }, + }} + > + + + + + + + + + + + + + Open Source, + + + Privacy First + + + + + + + + Alternative + + + + + + + Affine is the next-generation collaborative + knowledge base for professionals. + + + + + + + Try it on + + + + + + + + + + + + + It’s not just add-up of Docs, whiteboard, and + tables. + + + + + + + Transform any building blocks as you like. + + + Say goodbye to redundancy, and keep all your + knowledge minimal, in your way. + + + + + + + + Shape Your Page + + + Docs, kanban, and databases are all fully + functional at any place. You always keep + what-you-see-is-what-you-get. + + + All pages come with docs and whiteboard mode. + + + + + + + + + + + + + + Plan Your Task + + + No more chaos between so many views. + + + Set a TODO with Markdown, and manage it in + Kanban. + + + Managing multi-dimensional tables should be + simple as that. + + + + + + + + + + + + + Privacy-first, and collaborative. No compromises + whatsoever. + + + We don’t like cloud lock-in. Your data is always + locally stored and secured, while you can still + collaborate in real-time with others. + + + Privacy is the foundation of everything we do. But + it is no excuse for the bad experience. + + + + + + + + + + + + + + + + + Build for an open and semantic future + + + + + + + + Keep Updated on + + + + + + + + + Join Our Community + + + + + + + + + + + + + GitHub + + + + + + + + + + + + + Reddit + + + + + + + + + + + + + Telegram + + + + + + + + + + + + + Discord + + + + + + + + + AFFiNE is an + + #OpenSource + + company + + + + + + + Copyright © 2022 AFFiNE. + + + + + + ); +} + +export default App; diff --git a/apps/venus/src/assets/.gitkeep b/apps/venus/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/venus/src/assets/collaboration.png b/apps/venus/src/assets/collaboration.png new file mode 100644 index 0000000000..5d56d61581 Binary files /dev/null and b/apps/venus/src/assets/collaboration.png differ diff --git a/apps/venus/src/assets/discord.svg b/apps/venus/src/assets/discord.svg new file mode 100644 index 0000000000..9da6ee0330 --- /dev/null +++ b/apps/venus/src/assets/discord.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/apps/venus/src/assets/images/favicon.ico b/apps/venus/src/assets/images/favicon.ico new file mode 100644 index 0000000000..b972ff61cc Binary files /dev/null and b/apps/venus/src/assets/images/favicon.ico differ diff --git a/apps/venus/src/assets/logo.png b/apps/venus/src/assets/logo.png new file mode 100644 index 0000000000..54262b7cb2 Binary files /dev/null and b/apps/venus/src/assets/logo.png differ diff --git a/apps/venus/src/assets/page.png b/apps/venus/src/assets/page.png new file mode 100644 index 0000000000..2236ce046d Binary files /dev/null and b/apps/venus/src/assets/page.png differ diff --git a/apps/venus/src/assets/shape.png b/apps/venus/src/assets/shape.png new file mode 100644 index 0000000000..178d7ea6a1 Binary files /dev/null and b/apps/venus/src/assets/shape.png differ diff --git a/apps/venus/src/assets/task.png b/apps/venus/src/assets/task.png new file mode 100644 index 0000000000..8ef6c95057 Binary files /dev/null and b/apps/venus/src/assets/task.png differ diff --git a/apps/venus/src/environments/environment.prod.ts b/apps/venus/src/environments/environment.prod.ts new file mode 100644 index 0000000000..da7c84f638 --- /dev/null +++ b/apps/venus/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/venus/src/environments/environment.ts b/apps/venus/src/environments/environment.ts new file mode 100644 index 0000000000..855830c238 --- /dev/null +++ b/apps/venus/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/venus/src/favicon.ico b/apps/venus/src/favicon.ico new file mode 100644 index 0000000000..317ebcb233 Binary files /dev/null and b/apps/venus/src/favicon.ico differ diff --git a/apps/venus/src/index.html b/apps/venus/src/index.html new file mode 100644 index 0000000000..5dc4ff530d --- /dev/null +++ b/apps/venus/src/index.html @@ -0,0 +1,14 @@ + + + + + Venus + + + + + + +

+ + diff --git a/apps/venus/src/index.tsx b/apps/venus/src/index.tsx new file mode 100644 index 0000000000..c14c54bfa5 --- /dev/null +++ b/apps/venus/src/index.tsx @@ -0,0 +1,19 @@ +/* eslint-disable filename-rules/match */ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; + +import App from './app'; + +const container = document.getElementById('root'); +if (!container) { + throw new Error('No root container found'); +} +const root = createRoot(container); +root.render( + + + + + +); diff --git a/apps/venus/src/polyfills.ts b/apps/venus/src/polyfills.ts new file mode 100644 index 0000000000..2adf3d05b6 --- /dev/null +++ b/apps/venus/src/polyfills.ts @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/venus/src/template.html b/apps/venus/src/template.html new file mode 100644 index 0000000000..d0c4a84e0b --- /dev/null +++ b/apps/venus/src/template.html @@ -0,0 +1,11 @@ + + + + + + <%= htmlWebpackPlugin.options.title %> + + +
+ + diff --git a/apps/venus/tsconfig.app.json b/apps/venus/tsconfig.app.json new file mode 100644 index 0000000000..054e3ea559 --- /dev/null +++ b/apps/venus/tsconfig.app.json @@ -0,0 +1,23 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "jest.config.ts", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/apps/venus/tsconfig.json b/apps/venus/tsconfig.json new file mode 100644 index 0000000000..4aa485608c --- /dev/null +++ b/apps/venus/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "strictNullChecks": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/venus/tsconfig.spec.json b/apps/venus/tsconfig.spec.json new file mode 100644 index 0000000000..f60e49f7b8 --- /dev/null +++ b/apps/venus/tsconfig.spec.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ], + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ] +} diff --git a/apps/venus/webpack.config.js b/apps/venus/webpack.config.js new file mode 100644 index 0000000000..8c980aef98 --- /dev/null +++ b/apps/venus/webpack.config.js @@ -0,0 +1,237 @@ +const path = require('path'); +const zlib = require('zlib'); +const webpack = require('webpack'); +const getNxWebpackConfig = require('@nrwl/react/plugins/webpack'); +const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const CompressionPlugin = require('compression-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); +const Style9Plugin = require('style9/webpack'); + +const enableBundleAnalyzer = process.env.BUNDLE_ANALYZER; + +module.exports = function (webpackConfig) { + const config = getNxWebpackConfig(webpackConfig); + + const isProd = config.mode === 'production'; + + const style9 = { + test: /\.(tsx|ts|js|mjs|jsx)$/, + use: [ + { + loader: Style9Plugin.loader, + options: { + minifyProperties: isProd, + incrementalClassnames: isProd, + }, + }, + ], + }; + + config.experiments.topLevelAwait = true; + + if (isProd) { + config.module.rules.unshift(style9); + } else { + config.module.rules.push(style9); + } + + if (isProd) { + config.entry = { + main: [...config.entry.main, ...config.entry.polyfills], + }; + config.devtool = false; + config.output = { + ...config.output, + filename: '[name].[contenthash:8].js', + chunkFilename: '[name].[chunkhash:8].js', + hashFunction: undefined, + }; + config.optimization = { + nodeEnv: 'production', + minimize: true, + minimizer: [ + new TerserPlugin({ + terserOptions: { + ecma: 2020, + }, + extractComments: true, + parallel: true, + }), + new CssMinimizerPlugin(), + ], + splitChunks: { + chunks: 'all', + cacheGroups: { + styles: { + name: 'styles', + type: 'css/mini-extract', + chunks: 'all', + enforce: true, + }, + ui: { + test: /[\\/]node_modules[\\/](@mui|@emotion|react|katex)/, + name: 'ui', + priority: -9, + chunks: 'all', + }, + vender: { + test: /([\\/]node_modules[\\/]|polyfills|@nrwl)/, + name: 'vender', + priority: -10, + chunks: 'all', + }, + }, + }, + }; + config.module.rules.unshift({ + test: /\.css$/i, + use: [ + MiniCssExtractPlugin.loader, + { + loader: 'css-loader', + options: { + sourceMap: false, + }, + }, + ], + }); + config.module.rules.unshift({ + test: /\.scss$/i, + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + sourceMap: false, + }, + }, + { + loader: 'postcss-loader', + }, + ], + }); + config.module.rules.splice(6); + } else { + config.output = { + ...config.output, + publicPath: '/', + }; + + const babelLoader = config.module.rules.find( + rule => + typeof rule !== 'string' && + rule.loader?.toString().includes('babel-loader') + ); + if (babelLoader && typeof babelLoader !== 'string') { + babelLoader.options['plugins'] = [ + ...(babelLoader.options['plugins'] || []), + [require.resolve('babel-plugin-open-source')], + ]; + } + } + + addEmotionBabelPlugin(config); + + config.plugins = [ + ...config.plugins.filter( + p => !(isProd && p instanceof MiniCssExtractPlugin) + ), + new webpack.DefinePlugin({ + JWT_DEV: !isProd, + global: {}, + }), + isProd && + new HtmlWebpackPlugin({ + title: 'AFFiNE - All In One Workos', + favicon: path.resolve( + __dirname, + './src/assets/images/favicon.ico' + ), //favicon path + template: path.resolve(__dirname, './src/template.html'), + publicPath: '/', + }), + new Style9Plugin(), + isProd && new MiniCssExtractPlugin(), + isProd && + new CompressionPlugin({ + test: /\.(js|css|html|svg|ttf|woff)$/, + algorithm: 'brotliCompress', + filename: '[path][base].br', + compressionOptions: { + params: { + [zlib.constants.BROTLI_PARAM_QUALITY]: 11, + }, + }, + }), + isProd && + enableBundleAnalyzer && + new BundleAnalyzerPlugin({ analyzerMode: 'static' }), + ].filter(Boolean); + + // Workaround for webpack infinite recompile errors + config.watchOptions = { + // followSymlinks: false, + ignored: ['**/*.css'], + }; + + return config; +}; + +// TODO handle nx issue +// see https://github.com/nrwl/nx/issues/8870 +// see https://github.com/nrwl/nx/issues/4520#issuecomment-787473383 +const addEmotionBabelPlugin = config => { + const babelLoader = config.module.rules.find( + rule => + typeof rule !== 'string' && + rule.loader?.toString().includes('babel-loader') + ); + if (!babelLoader) { + return; + } + + babelLoader.options.plugins = [ + [ + require.resolve('@emotion/babel-plugin'), + { + // See https://github.com/mui/material-ui/issues/27380#issuecomment-928973157 + // See https://github.com/emotion-js/emotion/tree/main/packages/babel-plugin#importmap + importMap: { + '@toeverything/components/ui': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: [ + '@toeverything/components/ui', + 'styled', + ], + }, + }, + '@mui/material': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: ['@mui/material', 'styled'], + }, + }, + '@mui/material/styles': { + styled: { + canonicalImport: ['@emotion/styled', 'default'], + styledBaseImport: [ + '@mui/material/styles', + 'styled', + ], + }, + }, + }, + // sourceMap is on by default but source maps are dead code eliminated in production + sourceMap: true, + autoLabel: 'dev-only', + labelFormat: '[filename]-[local]', + cssPropOptimization: true, + }, + ], + ...(babelLoader.options.plugins ?? []), + ]; +}; diff --git a/libs/components/affine-board/src/hooks/use-shapes.ts b/libs/components/affine-board/src/hooks/use-shapes.ts index 8cbca07cc9..ecff1b8431 100644 --- a/libs/components/affine-board/src/hooks/use-shapes.ts +++ b/libs/components/affine-board/src/hooks/use-shapes.ts @@ -3,8 +3,12 @@ import { services } from '@toeverything/datasource/db-service'; import type { ReturnEditorBlock } from '@toeverything/datasource/db-service'; import type { TDShape } from '@toeverything/components/board-types'; import { Editor } from '@toeverything/components/board-shapes'; +import { usePageClientWidth } from '@toeverything/datasource/state'; export const useShapes = (workspace: string, rootBlockId: string) => { + const { pageClientWidth } = usePageClientWidth(); + // page padding left and right total 300px + const editorShapeInitSize = pageClientWidth - 300; const [blocks, setBlocks] = useState(); useEffect(() => { services.api.editorBlock @@ -58,8 +62,9 @@ export const useShapes = (workspace: string, rootBlockId: string) => { acc[block.id] = { ...shapeProps, id: block.id }; } else { acc[block.id] = Editor.getShape({ - point: [groupCount * 740, 200], + point: [groupCount * editorShapeInitSize + 200, 200], id: block.id, + size: [editorShapeInitSize, 200], ...shapeProps, affineId: shapeProps.affineId ?? block.id, workspace: block.workspace, diff --git a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx b/libs/components/board-shapes/src/editor-util/EditorUtil.tsx index 237224fe51..430a3069f2 100644 --- a/libs/components/board-shapes/src/editor-util/EditorUtil.tsx +++ b/libs/components/board-shapes/src/editor-util/EditorUtil.tsx @@ -64,18 +64,7 @@ export class EditorUtil extends TDShapeUtil { }; Component = TDShapeUtil.Component( - ( - { - shape, - meta: { - app: { useStore }, - }, - events, - isEditing, - onShapeChange, - }, - ref - ) => { + ({ shape, meta: { app }, events, isEditing, onShapeChange }, ref) => { const containerRef = useRef(); const { workspace, @@ -83,7 +72,7 @@ export class EditorUtil extends TDShapeUtil { size: [width, height], } = shape; - const state = useStore(); + const state = app.useStore(); const { currentPageId } = state.appState; const { editingId } = state.document.pageStates[currentPageId]; const { shapes } = state.document.pages[currentPageId]; @@ -134,11 +123,19 @@ export class EditorUtil extends TDShapeUtil { [isEditing] ); + const activateIfEditing = useCallback(() => { + if (editingText && editingId !== shape.id) { + app.setEditingText(shape.id); + } + }, [app, shape.id, editingText, editingId]); + return ( { ); }; + /** + * used for EditorUtil only + * @param id + * @returns + */ + setEditingText = (id: string) => { + if (this.readOnly) return; + + this.patchState( + { + document: { + pageStates: { + [this.currentPageId]: { + selectedIds: [id], + editingId: id, + }, + }, + }, + }, + `set_editing_id` + ); + }; + /** * Set or clear the hovered id * @param id [string] diff --git a/libs/components/board-types/src/types.ts b/libs/components/board-types/src/types.ts index 9f678921e9..fbf3e5c308 100644 --- a/libs/components/board-types/src/types.ts +++ b/libs/components/board-types/src/types.ts @@ -149,6 +149,7 @@ export interface TDMeta { isDarkMode: boolean; app: { useStore: () => TDSnapshot; + setEditingText: (id: string) => void; }; } 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 ce47f80e27..5fdd24788c 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 @@ -26,7 +26,6 @@ export const PendantHistoryPanel = ({ const { getProperties } = useRecastBlockMeta(); const { getProperty } = useRecastBlockMeta(); - const { getAllValue } = getRecastItemValue(block); const recastBlock = useRecastBlock(); const [history, setHistory] = useState([]); @@ -34,7 +33,7 @@ export const PendantHistoryPanel = ({ useEffect(() => { const init = async () => { - const currentBlockValues = getAllValue(); + const currentBlockValues = getRecastItemValue(block).getAllValue(); const allProperties = getProperties(); const missProperties = allProperties.filter( property => !currentBlockValues.find(v => v.id === property.id) @@ -52,24 +51,26 @@ export const PendantHistoryPanel = ({ return history; }, {}); - const blockHistory = await Promise.all( - Object.entries(historyMap).map( - async ([propertyId, blockId]) => { - const latestValueBlock = ( - await groupBlock.children() - ).find((block: AsyncBlock) => block.id === blockId); + const blockHistory = ( + await Promise.all( + Object.entries(historyMap).map( + async ([propertyId, blockId]) => { + const latestValueBlock = ( + await groupBlock.children() + ).find((block: AsyncBlock) => block.id === blockId); - return getRecastItemValue(latestValueBlock).getValue( - propertyId as RecastPropertyId - ); - } + return getRecastItemValue( + latestValueBlock + ).getValue(propertyId as RecastPropertyId); + } + ) ) - ); - + ).filter(v => v); setHistory(blockHistory); }; + init(); - }, [getAllValue, getProperties, groupBlock, recastBlock]); + }, [block, getProperties, groupBlock, recastBlock]); return ( 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 5458280f74..b9d29cebe7 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,11 +1,11 @@ -import React, { CSSProperties, useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { nanoid } from 'nanoid'; import { Input, Option, Select, Tooltip } from '@toeverything/components/ui'; import { HelpCenterIcon } from '@toeverything/components/icons'; import { AsyncBlock } from '../../editor'; import { IconMap, pendantOptions } from '../config'; -import { OptionType, PendantOptions, PendantTypes } from '../types'; +import { PendantOptions } from '../types'; import { PendantModifyPanel } from '../pendant-modify-panel'; import { StyledDivider, @@ -15,23 +15,13 @@ import { StyledPopoverSubTitle, StyledPopoverWrapper, } from '../StyledComponent'; -import { - genSelectOptionId, - InformationProperty, - useRecastBlock, - useRecastBlockMeta, - useSelectProperty, -} from '../../recast-block'; -import { - genInitialOptions, - getOfficialSelected, - getPendantConfigByType, -} from '../utils'; -import { usePendant } from '../use-pendant'; +import { genInitialOptions, getPendantConfigByType } from '../utils'; +import { useOnCreateSure } from './hooks'; const upperFirst = (str: string) => { return `${str[0].toUpperCase()}${str.slice(1)}`; }; + export const CreatePendantPanel = ({ block, onSure, @@ -41,9 +31,7 @@ export const CreatePendantPanel = ({ }) => { const [selectedOption, setSelectedOption] = useState(); const [fieldName, setFieldName] = useState(''); - const { addProperty, removeProperty } = useRecastBlockMeta(); - const { createSelect } = useSelectProperty(); - const { setPendant } = usePendant(block); + const onCreateSure = useOnCreateSure({ block }); useEffect(() => { selectedOption && @@ -110,91 +98,13 @@ export const CreatePendantPanel = ({ getPendantConfigByType(selectedOption.type) )} iconConfig={getPendantConfigByType(selectedOption.type)} - // isStatusSelect={selectedOption.name === 'Status'} onSure={async (type, newPropertyItem, newValue) => { - if (!fieldName) { - return; - } - - if ( - type === PendantTypes.MultiSelect || - type === PendantTypes.Select || - type === PendantTypes.Status - ) { - const newProperty = await createSelect({ - name: fieldName, - options: newPropertyItem, - type, - }); - - const selectedId = getOfficialSelected({ - isMulti: type === PendantTypes.MultiSelect, - options: newProperty.options, - tempOptions: newPropertyItem, - tempSelectedId: newValue, - }); - - await setPendant(newProperty, selectedId); - } else if (type === PendantTypes.Information) { - const emailOptions = genOptionWithId( - newPropertyItem.emailOptions - ); - - const phoneOptions = genOptionWithId( - newPropertyItem.phoneOptions - ); - - const locationOptions = genOptionWithId( - newPropertyItem.locationOptions - ); - - const newProperty = await addProperty({ - type, - name: fieldName, - emailOptions, - phoneOptions, - locationOptions, - } as Omit); - - await setPendant(newProperty, { - email: getOfficialSelected({ - isMulti: true, - options: emailOptions, - tempOptions: - newPropertyItem.emailOptions, - tempSelectedId: newValue.email, - }), - phone: getOfficialSelected({ - isMulti: true, - options: phoneOptions, - tempOptions: - newPropertyItem.phoneOptions, - tempSelectedId: newValue.phone, - }), - location: getOfficialSelected({ - isMulti: true, - options: locationOptions, - tempOptions: - newPropertyItem.locationOptions, - tempSelectedId: newValue.location, - }), - }); - } else { - // TODO: Color and background should use pendant config, but ui is not design now - const iconConfig = getPendantConfigByType(type); - // TODO: Color and background should be choose by user in the future - const newProperty = await addProperty({ - type: type, - name: fieldName, - background: - iconConfig.background as CSSProperties['background'], - color: iconConfig.color as CSSProperties['color'], - iconName: iconConfig.iconName, - }); - - await setPendant(newProperty, newValue); - } - + await onCreateSure({ + type, + newPropertyItem, + newValue, + fieldName, + }); onSure?.(); }} /> @@ -203,10 +113,3 @@ export const CreatePendantPanel = ({ ); }; - -const genOptionWithId = (options: OptionType[] = []) => { - return options.map((option: OptionType) => ({ - ...option, - id: genSelectOptionId(), - })); -}; 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 02b22e9f7e..bb51054964 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,27 +1,16 @@ import { useState } from 'react'; +import { Input, Tooltip } from '@toeverything/components/ui'; +import { HelpCenterIcon } from '@toeverything/components/icons'; import { PendantModifyPanel } from '../pendant-modify-panel'; import type { AsyncBlock } from '../../editor'; import { - genSelectOptionId, - InformationProperty, - type MultiSelectProperty, type RecastBlockValue, type RecastMetaProperty, - type SelectOption, - type SelectProperty, - useRecastBlockMeta, - useSelectProperty, } from '../../recast-block'; -import { OptionType, PendantTypes, TempInformationType } from '../types'; -import { - getOfficialSelected, - getPendantConfigByType, - // getPendantIconsConfigByNameOrType, -} from '../utils'; +import { getPendantConfigByType } from '../utils'; import { usePendant } from '../use-pendant'; import { StyledPopoverWrapper, - StyledOperationTitle, StyledOperationLabel, StyledInputEndAdornment, StyledDivider, @@ -29,10 +18,8 @@ import { StyledPopoverSubTitle, } from '../StyledComponent'; import { IconMap, pendantOptions } from '../config'; -import { Input, Tooltip } from '@toeverything/components/ui'; -import { HelpCenterIcon } from '@toeverything/components/icons'; -type SelectPropertyType = MultiSelectProperty | SelectProperty; +import { useOnUpdateSure } from './hooks'; type Props = { value: RecastBlockValue; @@ -53,13 +40,12 @@ export const UpdatePendantPanel = ({ onCancel, titleEditable = false, }: Props) => { - const { updateSelect } = useSelectProperty(); - const { setPendant, removePendant } = usePendant(block); const pendantOption = pendantOptions.find(v => v.type === property.type); const iconConfig = getPendantConfigByType(property.type); + const { removePendant } = usePendant(block); const Icon = IconMap[iconConfig.iconName]; - const { updateProperty } = useRecastBlockMeta(); - const [fieldTitle, setFieldTitle] = useState(property.name); + const [fieldName, setFieldName] = useState(property.name); + const onUpdateSure = useOnUpdateSure({ block, property }); return ( @@ -77,10 +63,10 @@ export const UpdatePendantPanel = ({ Field Title {titleEditable ? ( { - setFieldTitle(e.target.value); + setFieldName(e.target.value); }} endAdornment={ @@ -111,114 +97,12 @@ export const UpdatePendantPanel = ({ property={property} type={property.type} onSure={async (type, newPropertyItem, newValue) => { - if ( - type === PendantTypes.MultiSelect || - type === PendantTypes.Select || - type === PendantTypes.Status - ) { - const newOptions = newPropertyItem as OptionType[]; - let selectProperty = property as SelectPropertyType; - const deleteOptionIds = selectProperty.options - .filter(o => { - return !newOptions.find(no => no.id === o.id); - }) - .map(o => o.id); - const addOptions = newOptions.filter( - o => typeof o.id === 'number' - ); - - const { addSelectOptions, removeSelectOptions } = - updateSelect(selectProperty); - - deleteOptionIds.length && - (selectProperty = (await removeSelectOptions( - ...deleteOptionIds - )) as SelectPropertyType); - - addOptions.length && - (selectProperty = (await addSelectOptions( - ...(addOptions as unknown as Omit< - SelectOption, - 'id' - >[]) - )) as SelectPropertyType); - - const selectedId = getOfficialSelected({ - isMulti: type === PendantTypes.MultiSelect, - options: selectProperty.options, - tempOptions: newPropertyItem, - tempSelectedId: newValue, - }); - - await setPendant(selectProperty, selectedId); - } else if (type === PendantTypes.Information) { - // const { emailOptions, phoneOptions, locationOptions } = - // property as InformationProperty; - const optionGroup = - newPropertyItem as TempInformationType; - - const emailOptions = optionGroup.emailOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - const phoneOptions = optionGroup.phoneOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - const locationOptions = optionGroup.locationOptions.map( - option => { - if (typeof option.id === 'number') { - option.id = genSelectOptionId(); - } - return option; - } - ); - - const newProperty = await updateProperty({ - ...property, - emailOptions, - phoneOptions, - locationOptions, - } as InformationProperty); - - await setPendant(newProperty, { - email: getOfficialSelected({ - isMulti: true, - options: emailOptions as SelectOption[], - tempOptions: newPropertyItem.emailOptions, - tempSelectedId: newValue.email, - }), - phone: getOfficialSelected({ - isMulti: true, - options: phoneOptions as SelectOption[], - tempOptions: newPropertyItem.phoneOptions, - tempSelectedId: newValue.phone, - }), - location: getOfficialSelected({ - isMulti: true, - options: locationOptions as SelectOption[], - tempOptions: newPropertyItem.locationOptions, - tempSelectedId: newValue.location, - }), - }); - } else { - await setPendant(property, newValue); - } - - if (fieldTitle !== property.name) { - await updateProperty({ - ...property, - name: fieldTitle, - }); - } + await onUpdateSure({ + type, + newPropertyItem, + newValue, + fieldName, + }); onSure?.(); }} onDelete={ 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 new file mode 100644 index 0000000000..83889962ac --- /dev/null +++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts @@ -0,0 +1,265 @@ +import type { CSSProperties } from 'react'; +import { + genSelectOptionId, + type InformationProperty, + type MultiSelectProperty, + type RecastMetaProperty, + type SelectOption, + type SelectProperty, + useRecastBlockMeta, + useSelectProperty, +} from '../../recast-block'; +import { type AsyncBlock } from '../../editor'; +import { usePendant } from '../use-pendant'; +import { + type OptionType, + PendantTypes, + type TempInformationType, +} from '../types'; +import { + checkPendantForm, + getOfficialSelected, + getPendantConfigByType, +} from '../utils'; +import { message } from '@toeverything/components/ui'; + +type SelectPropertyType = MultiSelectProperty | SelectProperty; +type SureParams = { + fieldName: string; + type: PendantTypes; + newPropertyItem: any; + newValue: any; +}; + +const genOptionWithId = (options: OptionType[] = []) => { + return options.map((option: OptionType) => ({ + ...option, + id: genSelectOptionId(), + })); +}; +// Callback function for pendant create +export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => { + const { addProperty } = useRecastBlockMeta(); + const { createSelect } = useSelectProperty(); + const { setPendant } = usePendant(block); + + return async ({ + type, + fieldName, + 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 || + type === PendantTypes.Status + ) { + const newProperty = await createSelect({ + name: fieldName, + options: newPropertyItem, + type, + }); + + const selectedId = getOfficialSelected({ + isMulti: type === PendantTypes.MultiSelect, + options: newProperty.options, + tempOptions: newPropertyItem, + tempSelectedId: newValue, + }); + + await setPendant(newProperty, selectedId); + } else if (type === PendantTypes.Information) { + const emailOptions = genOptionWithId(newPropertyItem.emailOptions); + + const phoneOptions = genOptionWithId(newPropertyItem.phoneOptions); + + const locationOptions = genOptionWithId( + newPropertyItem.locationOptions + ); + + const newProperty = await addProperty({ + type, + name: fieldName, + emailOptions, + phoneOptions, + locationOptions, + } as Omit); + + await setPendant(newProperty, { + email: getOfficialSelected({ + isMulti: true, + options: emailOptions, + tempOptions: newPropertyItem.emailOptions, + tempSelectedId: newValue.email, + }), + phone: getOfficialSelected({ + isMulti: true, + options: phoneOptions, + tempOptions: newPropertyItem.phoneOptions, + tempSelectedId: newValue.phone, + }), + location: getOfficialSelected({ + isMulti: true, + options: locationOptions, + tempOptions: newPropertyItem.locationOptions, + tempSelectedId: newValue.location, + }), + }); + } else { + // TODO: Color and background should use pendant config, but ui is not design now + const iconConfig = getPendantConfigByType(type); + // TODO: Color and background should be choose by user in the future + const newProperty = await addProperty({ + type: type, + name: fieldName, + background: + iconConfig.background as CSSProperties['background'], + color: iconConfig.color as CSSProperties['color'], + iconName: iconConfig.iconName, + }); + + await setPendant(newProperty, newValue); + } + }; +}; + +// Callback function for pendant update +export const useOnUpdateSure = ({ + block, + property, +}: { + block: AsyncBlock; + property: RecastMetaProperty; +}) => { + const { updateSelect } = useSelectProperty(); + const { setPendant } = usePendant(block); + const { updateProperty } = useRecastBlockMeta(); + + return async ({ + type, + fieldName, + 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 || + type === PendantTypes.Status + ) { + const newOptions = newPropertyItem as OptionType[]; + let selectProperty = property as SelectPropertyType; + const deleteOptionIds = selectProperty.options + .filter(o => { + return !newOptions.find(no => no.id === o.id); + }) + .map(o => o.id); + const addOptions = newOptions.filter(o => typeof o.id === 'number'); + + const { addSelectOptions, removeSelectOptions } = + updateSelect(selectProperty); + + deleteOptionIds.length && + (selectProperty = (await removeSelectOptions( + ...deleteOptionIds + )) as SelectPropertyType); + + addOptions.length && + (selectProperty = (await addSelectOptions( + ...(addOptions as unknown as Omit[]) + )) as SelectPropertyType); + + const selectedId = getOfficialSelected({ + isMulti: type === PendantTypes.MultiSelect, + options: selectProperty.options, + tempOptions: newPropertyItem, + tempSelectedId: newValue, + }); + + await setPendant(selectProperty, selectedId); + } else if (type === PendantTypes.Information) { + // const { emailOptions, phoneOptions, locationOptions } = + // property as InformationProperty; + const optionGroup = newPropertyItem as TempInformationType; + + const emailOptions = optionGroup.emailOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + const phoneOptions = optionGroup.phoneOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + const locationOptions = optionGroup.locationOptions.map(option => { + if (typeof option.id === 'number') { + option.id = genSelectOptionId(); + } + return option; + }); + + const newProperty = await updateProperty({ + ...property, + emailOptions, + phoneOptions, + locationOptions, + } as InformationProperty); + + await setPendant(newProperty, { + email: getOfficialSelected({ + isMulti: true, + options: emailOptions as SelectOption[], + tempOptions: newPropertyItem.emailOptions, + tempSelectedId: newValue.email, + }), + phone: getOfficialSelected({ + isMulti: true, + options: phoneOptions as SelectOption[], + tempOptions: newPropertyItem.phoneOptions, + tempSelectedId: newValue.phone, + }), + location: getOfficialSelected({ + isMulti: true, + options: locationOptions as SelectOption[], + tempOptions: newPropertyItem.locationOptions, + tempSelectedId: newValue.location, + }), + }); + } else { + await setPendant(property, newValue); + } + + if (fieldName !== property.name) { + await updateProperty({ + ...property, + name: fieldName, + }); + } + }; +}; diff --git a/libs/components/editor-core/src/block-pendant/utils.ts b/libs/components/editor-core/src/block-pendant/utils.ts index 33b59fdc50..d2807dbb85 100644 --- a/libs/components/editor-core/src/block-pendant/utils.ts +++ b/libs/components/editor-core/src/block-pendant/utils.ts @@ -1,4 +1,5 @@ import { + PropertyType, RecastBlockValue, RecastPropertyId, SelectOption, @@ -175,3 +176,49 @@ export const genInitialOptions = ( } return [genBasicOption({ index: 0, iconConfig })]; }; + +export const checkPendantForm = ( + type: PropertyType, + fieldTitle: string, + newProperty: any, + newValue: any +): { passed: boolean; message: string } => { + if (!fieldTitle) { + return { passed: false, message: 'The field title cannot be empty !' }; + } + + if ( + type === PendantTypes.MultiSelect || + type === PendantTypes.Select || + type === PendantTypes.Status + ) { + if (!newProperty) { + return { + passed: false, + message: 'Ensure at least one non-empty option !', + }; + } + } + if (type === PendantTypes.Information) { + if (!newProperty) { + return { + passed: false, + message: 'Ensure at least one non-empty option !', + }; + } + } + if ( + type === PendantTypes.Text || + type === PendantTypes.Date || + type === PendantTypes.Mention + ) { + if (!newValue) { + return { + passed: false, + message: `The content of the input must not be empty !`, + }; + } + } + + return { passed: true, message: 'Check passed !' }; +}; diff --git a/libs/components/editor-core/src/editor/scroll/scroll.ts b/libs/components/editor-core/src/editor/scroll/scroll.ts index efe65e50c0..81f66578e4 100644 --- a/libs/components/editor-core/src/editor/scroll/scroll.ts +++ b/libs/components/editor-core/src/editor/scroll/scroll.ts @@ -297,10 +297,10 @@ export class ScrollManager { } public lock() { - this._scrollController.lockScroll(); + this._scrollController?.lockScroll(); } public unLock() { - this._scrollController.unLockScroll(); + this._scrollController?.unLockScroll(); } } diff --git a/libs/components/editor-core/src/editor/types.ts b/libs/components/editor-core/src/editor/types.ts index f412417014..797d68f46c 100644 --- a/libs/components/editor-core/src/editor/types.ts +++ b/libs/components/editor-core/src/editor/types.ts @@ -23,6 +23,7 @@ import type { DragDropManager } from './drag-drop'; import { MouseManager } from './mouse'; import { Observable } from 'rxjs'; import { Point } from '@toeverything/utils'; +import { ScrollManager } from './scroll'; // import { BrowserClipboard } from './clipboard/browser-clipboard'; @@ -63,6 +64,7 @@ export interface VirgoSelection { // Editor's external API export interface Virgo { selectionManager: SelectionManager; + scrollManager: ScrollManager; createBlock: ( type: keyof BlockFlavors, parentId?: string 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 1cd22e48e2..78b6ef5ddd 100644 --- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx +++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx @@ -40,26 +40,30 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { bottom: 0, }); - const [search_text, set_search_text] = useState(''); - const [search_blocks, set_search_blocks] = useState([]); + const [searchText, setSearchText] = useState(''); + const [searchBlocks, setSearchBlocks] = useState([]); const commandMenuContentRef = useRef(); // TODO: Two-way link to be developed // useEffect(() => { - // QueryBlocks(editor, search_text, result => set_search_blocks(result)); - // }, [editor, search_text]); + // QueryBlocks(editor, searchText, result => set_searchBlocks(result)); + // }, [editor, searchText]); + const hideMenu = () => { + setShow(false); + editor.scrollManager.unLock(); + }; const [types, categories] = useMemo(() => { const types: Array = []; const categories: Array = []; - if (search_blocks.length) { - Object.values(search_blocks).forEach(({ id }) => types.push(id)); + if (searchBlocks.length) { + Object.values(searchBlocks).forEach(({ id }) => types.push(id)); categories.push(CommandMenuCategories.pages); } Object.entries(menuItemsMap).forEach(([category, itemInfoList]) => { itemInfoList.forEach(info => { if ( - !search_text || - info.text.toLowerCase().includes(search_text.toLowerCase()) + !searchText || + info.text.toLowerCase().includes(searchText.toLowerCase()) ) { types.push(info.type); } @@ -73,9 +77,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { }); }); return [types, categories]; - }, [search_blocks, search_text]); + }, [searchBlocks, searchText]); - const check_if_show_command_menu = useCallback( + const checkIfShowCommandMenu = useCallback( async (event: React.KeyboardEvent) => { const { type, anchorNode } = editor.selection.currentSelectInfo; if (event.key === '/' && type === 'Range') { @@ -101,8 +105,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { ); } }); - set_search_text(''); + setSearchText(''); setShow(true); + editor.scrollManager.lock(); const rect = editor.selection.currentSelectInfo?.browserSelection ?.getRangeAt(0) @@ -137,12 +142,12 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { [editor, blockId] ); - const handle_click_others = useCallback( + const handleClickOthers = useCallback( (event: React.KeyboardEvent) => { if (show) { const { anchorNode } = editor.selection.currentSelectInfo; if (anchorNode.id !== blockId) { - setShow(false); + hideMenu(); return; } setTimeout(() => { @@ -150,12 +155,12 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { editor.blockHelper.getSearchSlashText(blockId); // check if has search text if (searchText && searchText.startsWith('/')) { - set_search_text(searchText.slice(1)); + setSearchText(searchText.slice(1)); } else { - setShow(false); + hideMenu(); } if (searchText.length > 6 && !types.length) { - setShow(false); + hideMenu(); } }); } @@ -163,18 +168,18 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { [editor, show, blockId, types] ); - const handle_keyup = useCallback( + const handleKeyup = useCallback( (event: React.KeyboardEvent) => { - check_if_show_command_menu(event); - handle_click_others(event); + checkIfShowCommandMenu(event); + handleClickOthers(event); }, - [check_if_show_command_menu, handle_click_others] + [checkIfShowCommandMenu, handleClickOthers] ); - const handle_key_down = useCallback( + const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { if (event.code === 'Escape') { - setShow(false); + hideMenu(); } }, [] @@ -183,23 +188,23 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { useEffect(() => { const sub = hooks .get(HookType.ON_ROOT_NODE_KEYUP) - .subscribe(handle_keyup); + .subscribe(handleKeyup); sub.add( hooks .get(HookType.ON_ROOT_NODE_KEYDOWN_CAPTURE) - .subscribe(handle_key_down) + .subscribe(handleKeyDown) ); return () => { sub.unsubscribe(); }; - }, [handle_keyup, handle_key_down, hooks]); + }, [handleKeyup, handleKeyDown, hooks]); - const handle_click_away = () => { - setShow(false); + const handleClickAway = () => { + hideMenu(); }; - const handle_selected = async (type: BlockFlavorKeys | string) => { + const handleSelected = async (type: BlockFlavorKeys | string) => { const text = await editor.commands.textCommands.getBlockText(blockId); editor.blockHelper.removeSearchSlash(blockId, true); if (type.startsWith('Virgo')) { @@ -224,21 +229,21 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => { block.firstCreateFlag = true; } } - setShow(false); + hideMenu(); }; - const handle_close = () => { + const handleClose = () => { editor.blockHelper.removeSearchSlash(blockId); }; return (
{show ? ( - +
{ }} isShow={show} blockId={blockId} - onSelected={handle_selected} - onclose={handle_close} - searchBlocks={search_blocks} + onSelected={handleSelected} + onclose={handleClose} + searchBlocks={searchBlocks} types={types} categories={categories} /> diff --git a/libs/components/icons/src/auto-icons/add/add.svg b/libs/components/icons/src/auto-icons/add/add.svg new file mode 100644 index 0000000000..70cc37d737 --- /dev/null +++ b/libs/components/icons/src/auto-icons/add/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/components/icons/src/auto-icons/add/add.tsx b/libs/components/icons/src/auto-icons/add/add.tsx new file mode 100644 index 0000000000..59e6fc0721 --- /dev/null +++ b/libs/components/icons/src/auto-icons/add/add.tsx @@ -0,0 +1,21 @@ + +import { FC } from 'react'; +// eslint-disable-next-line no-restricted-imports +import { SvgIcon } from '@mui/material'; +// eslint-disable-next-line no-restricted-imports +import type { SvgIconProps } from '@mui/material'; + +export interface AddIconProps extends Omit { + color?: string +} + +export const AddIcon: FC = ({ color, style, ...props}) => { + const propsStyles = {"color": color}; + const customStyles = {}; + const styles = {...propsStyles, ...customStyles, ...style} + return ( + + + + ) +}; diff --git a/libs/components/icons/src/auto-icons/index.ts b/libs/components/icons/src/auto-icons/index.ts index 0cfce81327..4ea171d7b5 100644 --- a/libs/components/icons/src/auto-icons/index.ts +++ b/libs/components/icons/src/auto-icons/index.ts @@ -1,4 +1,4 @@ -export const timestamp = 1659000896562; +export const timestamp = 1659423582387; export * from './image/image'; export * from './format-clear/format-clear'; export * from './backward-undo/backward-undo'; @@ -125,4 +125,8 @@ export * from './eraser/eraser'; export * from './group-by/group-by'; export * from './layout/layout'; export * from './lock/lock'; -export * from './unlock/unlock'; \ No newline at end of file +export * from './unlock/unlock'; +export * from './more-tags-an-subblocks/more-tags-an-subblocks'; +export * from './page-in-page-tree/page-in-page-tree'; +export * from './pin/pin'; +export * from './add/add'; \ No newline at end of file diff --git a/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.svg b/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.svg new file mode 100644 index 0000000000..de4cfcdf98 --- /dev/null +++ b/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.tsx b/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.tsx new file mode 100644 index 0000000000..c631600a86 --- /dev/null +++ b/libs/components/icons/src/auto-icons/more-tags-an-subblocks/more-tags-an-subblocks.tsx @@ -0,0 +1,21 @@ + +import { FC } from 'react'; +// eslint-disable-next-line no-restricted-imports +import { SvgIcon } from '@mui/material'; +// eslint-disable-next-line no-restricted-imports +import type { SvgIconProps } from '@mui/material'; + +export interface MoreTagsAnSubblocksIconProps extends Omit { + color?: string +} + +export const MoreTagsAnSubblocksIcon: FC = ({ color, style, ...props}) => { + const propsStyles = {"color": color}; + const customStyles = {}; + const styles = {...propsStyles, ...customStyles, ...style} + return ( + + + + ) +}; diff --git a/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.svg b/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.svg new file mode 100644 index 0000000000..be6b6bca1e --- /dev/null +++ b/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.tsx b/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.tsx new file mode 100644 index 0000000000..9d404fb4ed --- /dev/null +++ b/libs/components/icons/src/auto-icons/page-in-page-tree/page-in-page-tree.tsx @@ -0,0 +1,21 @@ + +import { FC } from 'react'; +// eslint-disable-next-line no-restricted-imports +import { SvgIcon } from '@mui/material'; +// eslint-disable-next-line no-restricted-imports +import type { SvgIconProps } from '@mui/material'; + +export interface PageInPageTreeIconProps extends Omit { + color?: string +} + +export const PageInPageTreeIcon: FC = ({ color, style, ...props}) => { + const propsStyles = {"color": color}; + const customStyles = {}; + const styles = {...propsStyles, ...customStyles, ...style} + return ( + + + + ) +}; diff --git a/libs/components/icons/src/auto-icons/pin/pin.svg b/libs/components/icons/src/auto-icons/pin/pin.svg new file mode 100644 index 0000000000..49bc3a0852 --- /dev/null +++ b/libs/components/icons/src/auto-icons/pin/pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/libs/components/icons/src/auto-icons/pin/pin.tsx b/libs/components/icons/src/auto-icons/pin/pin.tsx new file mode 100644 index 0000000000..60f1db57f2 --- /dev/null +++ b/libs/components/icons/src/auto-icons/pin/pin.tsx @@ -0,0 +1,21 @@ + +import { FC } from 'react'; +// eslint-disable-next-line no-restricted-imports +import { SvgIcon } from '@mui/material'; +// eslint-disable-next-line no-restricted-imports +import type { SvgIconProps } from '@mui/material'; + +export interface PinIconProps extends Omit { + color?: string +} + +export const PinIcon: FC = ({ color, style, ...props}) => { + const propsStyles = {"color": color}; + const customStyles = {}; + const styles = {...propsStyles, ...customStyles, ...style} + return ( + + + + ) +}; diff --git a/libs/components/layout/src/header/LayoutHeader.tsx b/libs/components/layout/src/header/LayoutHeader.tsx index ffc061a324..0218c292bd 100644 --- a/libs/components/layout/src/header/LayoutHeader.tsx +++ b/libs/components/layout/src/header/LayoutHeader.tsx @@ -3,13 +3,14 @@ import { LogoIcon, SideBarViewIcon, SearchIcon, + SideBarViewCloseIcon, } from '@toeverything/components/icons'; import { useShowSettingsSidebar } from '@toeverything/datasource/state'; import { CurrentPageTitle } from './Title'; import { EditorBoardSwitcher } from './EditorBoardSwitcher'; export const LayoutHeader = () => { - const { toggleSettingsSidebar: toggleInfoSidebar } = + const { toggleSettingsSidebar: toggleInfoSidebar, showSettingsSidebar } = useShowSettingsSidebar(); return ( @@ -31,7 +32,11 @@ export const LayoutHeader = () => {
- + {showSettingsSidebar ? ( + + ) : ( + + )} diff --git a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx index 3de73c78df..b0527c5e42 100644 --- a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx +++ b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx @@ -50,31 +50,35 @@ export const Activities = () => { const [recentPages, setRecentPages] = useState([]); const userId = user?.id; - const fetchRecentPages = useCallback(async () => { - if (!userId || !currentSpaceId) { - return; - } - const recent_pages = await services.api.userConfig.getRecentPages( - currentSpaceId, - userId - ); - setRecentPages(recent_pages); - }, [userId, currentSpaceId]); + /* temporarily remove:show recently viewed documents */ + // const fetchRecentPages = useCallback(async () => { + // if (!userId || !currentSpaceId) { + // return; + // } + // const recent_pages = await services.api.userConfig.getRecentPages( + // currentSpaceId, + // userId + // ); + // setRecentPages(recent_pages); + // }, [userId, currentSpaceId]); - useEffect(() => { - (async () => { - await fetchRecentPages(); - })(); - }, [fetchRecentPages]); + // useEffect(() => { + // (async () => { + // await fetchRecentPages(); + // })(); + // }, [fetchRecentPages]); + + /* show recently edit documents */ + const getRecentEditPages = async (state, block) => { + console.log(state, await block.children()); + }; useEffect(() => { let unobserve: () => void; const observe = async () => { unobserve = await services.api.userConfig.observe( { workspace: currentSpaceId }, - () => { - fetchRecentPages(); - } + getRecentEditPages ); }; observe(); @@ -82,7 +86,7 @@ export const Activities = () => { return () => { unobserve?.(); }; - }, [currentSpaceId, fetchRecentPages]); + }, [currentSpaceId, getRecentEditPages]); return ( 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 11ba52b018..0c58d60fb0 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx +++ b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx @@ -95,30 +95,37 @@ export function DndTree(props: DndTreeProps) { > {/* */} {flattenedItems.map( - ({ id, title, children, collapsed, depth }) => ( - handleCollapse(id) - : undefined - } - onRemove={ - removable ? () => handleRemove(id) : undefined - } - /> - ) + ({ id, title, children, collapsed, depth }) => { + return ( + handleCollapse(id) + : undefined + } + onRemove={ + removable + ? () => handleRemove(id) + : undefined + } + /> + ); + } )} void; } + +const StyledAction = styled('div')({ + display: 'flex', + alignItems: 'center', +}); + function DndTreeItemMoreActions(props: ActionsProps) { const [alert_open, set_alert_open] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState(null); @@ -233,29 +244,42 @@ function DndTreeItemMoreActions(props: ActionsProps) { ]; return ( - <> - - ··· - - - - + handleClose()}> +
+
+ + + + + + + + +
+ + +
+
); } 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 fe0b044db6..a524dff76e 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 @@ -65,7 +65,7 @@ export const TreeItem = forwardRef( const navigate = useNavigate(); const BooleanPageTreeItemMoreActions = useFlag( 'BooleanPageTreeItemMoreActions', - false + true ); return (
  • ( title={value} > - {collapsed ? : } + {childCount !== 0 && + (collapsed ? ( + + ) : ( + + ))} {/**/} {/* */} diff --git a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/tree-item.module.scss b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/tree-item.module.scss index 7af3eda1c1..6fb0d37a57 100755 --- a/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/tree-item.module.scss +++ b/libs/components/layout/src/workspace-sidebar/page-tree/tree-item/tree-item.module.scss @@ -83,7 +83,7 @@ justify-content: space-around; background-color: #fff; color: #4c6275; - padding: 0 0.5rem; + padding-right: 0.5rem; overflow: hidden; .TreeItemMoreActions { diff --git a/libs/components/ui/src/button/IconButton.tsx b/libs/components/ui/src/button/IconButton.tsx index 1dd7606f09..77e1f08a6e 100644 --- a/libs/components/ui/src/button/IconButton.tsx +++ b/libs/components/ui/src/button/IconButton.tsx @@ -36,6 +36,7 @@ interface IconButtonProps { style?: CSSProperties; className?: string; size?: SizeType; + hoverColor?: string; } export const IconButton: FC> = ({ @@ -57,47 +58,48 @@ export const IconButton: FC> = ({ ); }; -const Container = styled('button')<{ size?: SizeType }>( - ({ theme, size = SIZE_MIDDLE }) => { - const { iconSize, areaSize } = SIZE_CONFIG[size]; +const Container = styled('button')<{ + size?: SizeType; + hoverColor?: string; +}>(({ theme, size = SIZE_MIDDLE, hoverColor }) => { + const { iconSize, areaSize } = SIZE_CONFIG[size]; - return { + return { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: areaSize, + height: areaSize, + backgroundColor: 'transparent', + color: theme.affine.palette.icons, + padding: theme.affine.spacing.iconPadding, + borderRadius: '3px', + + '& svg': { + width: iconSize, + height: iconSize, display: 'flex', justifyContent: 'center', alignItems: 'center', - width: areaSize, - height: areaSize, - backgroundColor: theme.affine.palette.white, - color: theme.affine.palette.icons, - padding: theme.affine.spacing.iconPadding, - borderRadius: '5px', + }, - '& svg': { - width: iconSize, - height: iconSize, - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - }, + '&:hover': { + backgroundColor: hoverColor || theme.affine.palette.hover, + }, - '&:hover': { - backgroundColor: theme.affine.palette.hover, - }, + [`&${buttonStatus.hover}`]: { + backgroundColor: theme.affine.palette.hover, + }, - [`&${buttonStatus.hover}`]: { - backgroundColor: theme.affine.palette.hover, - }, + '&:focus': { + color: theme.affine.palette.primary, + }, + [`&.${buttonStatus.focus}`]: { + color: theme.affine.palette.primary, + }, - '&:focus': { - color: theme.affine.palette.primary, - }, - [`&.${buttonStatus.focus}`]: { - color: theme.affine.palette.primary, - }, - - [`&${buttonStatus.disabled}`]: { - cursor: 'not-allowed', - }, - }; - } -); + [`&${buttonStatus.disabled}`]: { + cursor: 'not-allowed', + }, + }; +}); diff --git a/libs/datasource/db-service/src/services/editor-block/templates/blog.json b/libs/datasource/db-service/src/services/editor-block/templates/blog.json new file mode 100644 index 0000000000..bd4d745adb --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/blog.json @@ -0,0 +1,237 @@ +{ + "type": "group", + "properties": {}, + "blocks": [ + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "As a collaborative real-time editor, AFFiNE aims to resolve problems in three situations:" + } + ] + } + }, + "blocks": [] + }, + { + "type": "bullet", + "properties": { + "text": { + "value": [ + { + "text": "Multi-master replication: the synchronization of data between equipment and applications;" + } + ] + } + }, + "blocks": [] + }, + { + "type": "bullet", + "properties": { + "text": { + "value": [ + { + "text": "Eventual consistency: the consistence of data regardless of network latency and outage;" + } + ] + } + }, + "blocks": [] + }, + { + "type": "bullet", + "properties": { + "text": { + "value": [ + { + "text": "Conflict resolution: the resolution of conflict between simultaneous edits." + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "To achieve these aims, a proper collaborative algorithm should be used. There are hundreds of collaborative algorithms being invented over the past three decades, but they usually fall into two categories: either operational transformation (OT) or conflict-free replicated data type (CRDT). We think CRDT is a better choice." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading1", + "properties": { + "text": { + "value": [{ "bold": true, "text": "What does CRDT do" }] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "CRDT is capable of discovering and resolving conflicts while ensuring the effective distribution and merge of date. It may sound like magic, but think about historical study, it is very possible to discover the truth from scattered evidence." + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "For CRDT, every piece of data is like a \"historic fragment\". We keep collecting the fragments from other clients and then restore the truth by excluding repeated data and correcting false information." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading1", + "properties": { + "text": { + "value": [{ "bold": true, "text": "Why CRDT is better" }] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "In contrast to OT, CRDT possesses three big advantages:" + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading2", + "properties": { + "text": { + "value": [{ "bold": true, "text": "Flexibility" }] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "CRDT supports more data types. For example, Yjs supports Array, Map, and Treelike, and therefore applies to more business scenarios." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading2", + "properties": { + "text": { + "value": [{ "bold": true, "text": "Performance" }] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "CRDT tolerates higher latency and can wait longer for solving conflicts, whereas the calculation of OT in the same condition may become too overwhelming for a server to sustain." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading2", + "properties": { + "text": { + "value": [{ "bold": true, "text": "Extensibility" }] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "Because CRDT supports more data types and editor elements, it is more extensible." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading1", + "properties": { + "text": { "value": [{ "text": "Conclusion" }] } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice." + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { "text": { "value": [{ "text": "" }] } }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { "text": "" }, + { + "type": "link", + "url": "https://blog.affine.pro/", + "id": "link.stubssslo0rq", + "children": [{ "text": "← View all posts" }] + }, + { "text": "" } + ] + } + }, + "blocks": [] + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/empty.json b/libs/datasource/db-service/src/services/editor-block/templates/empty.json new file mode 100644 index 0000000000..763095a964 --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/empty.json @@ -0,0 +1,15 @@ +{ + "type": "group", + "properties": {}, + "blocks": [ + { + "type": "text", + "properties": { + "text": { + "value": [{ "text": "" }] + } + }, + "blocks": [] + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/grid.json b/libs/datasource/db-service/src/services/editor-block/templates/grid.json new file mode 100644 index 0000000000..f97c4e425b --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/grid.json @@ -0,0 +1,159 @@ +{ + "type": "group", + "properties": {}, + "blocks": [ + { + "type": "heading2", + "properties": { + "text": { + "value": [ + { + "bold": true, + "text": "Performance" + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "CRDT tolerates higher latency and can wait longer for solving conflicts, whereas the calculation of OT in the same condition may become too overwhelming for a server to sustain." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading2", + "properties": { + "text": { + "value": [ + { + "bold": true, + "text": "Extensibility" + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "Because CRDT supports more data types and editor elements, it is more extensible." + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading1", + "properties": { + "text": { + "value": [ + { + "text": "Conclusion" + } + ] + } + }, + "blocks": [] + }, + { + "type": "grid", + "properties": {}, + "blocks": [ + { + "type": "gridItem", + "properties": { + "gridItemWidth": "50%" + }, + "blocks": [ + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice." + } + ] + } + }, + "blocks": [] + } + ] + }, + { + "type": "gridItem", + "properties": { + "gridItemWidth": "50%" + }, + "blocks": [ + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice." + } + ] + } + }, + "blocks": [] + } + ] + } + ] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "" + } + ] + } + }, + "blocks": [] + }, + { + "type": "text", + "properties": { + "text": { + "value": [ + { + "text": "" + }, + { + "type": "link", + "url": "https://blog.affine.pro/", + "id": "link.stubssslo0rq", + "children": [ + { + "text": "← View all posts" + } + ] + }, + { + "text": "" + } + ] + } + } + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/group-templates.ts b/libs/datasource/db-service/src/services/editor-block/templates/group-templates.ts deleted file mode 100644 index 3888b54333..0000000000 --- a/libs/datasource/db-service/src/services/editor-block/templates/group-templates.ts +++ /dev/null @@ -1,592 +0,0 @@ -//@ts-nocheck -import { GroupTemplate } from './types'; -type GroupTemplateMap = Record; -const groupTemplateMap: GroupTemplateMap = { - empty: { - type: 'group', - properties: {}, - blocks: [ - { - type: 'text', - properties: { - text: { - value: [{ text: '' }], - }, - }, - blocks: [], - }, - ], - }, - todolist: { - type: 'group', - properties: {}, - blocks: [ - { - type: 'heading1', - properties: { - text: { - value: [{ text: '🎓Graduating from the project' }], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { - text: 'Congratulations! Now you can start create your own projects!', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { - text: 'To start using workspaces and folders hit the ', - }, - { - bold: true, - text: 'button at the top left of the screen', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { - text: '', - }, - { - text: 'At any time if you feel lost do visit our 📚Blog: ', - }, - { - type: 'link', - url: 'https://blog.affine.pro/', - id: 'link.qx4yhw81or54', - children: [ - { - text: 'https://blog.affine.pro', - }, - ], - }, - { - text: '', - }, - ], - }, - collapsed: { - value: false, - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { - text: '', - }, - { - text: 'If you have any suggestions drop a post in our Reddit Channel:', - }, - { - type: 'link', - url: 'https://www.reddit.com/r/Affine/', - id: 'link.zeafc4ogfvrb', - children: [ - { - text: 'https://www.reddit.com/r/Affine/', - }, - ], - }, - { - text: ' ', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading2', - properties: { - text: { - value: [ - { - text: '🎉 The Essentials. Check things off after you tried them!', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { text: ' ✅ ' }, - { bold: true, text: 'Check' }, - { - text: ' the text box here to complete the task!', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { text: '' }, - { text: '' }, - { text: ' 👋 ' }, - { bold: true, text: 'Drag' }, - { - text: ' the ⠟ button left of the checkbox to reorder tasks', - }, - { text: '' }, - { text: '' }, - ], - }, - }, - blocks: [], - }, - { - type: 'todo', - properties: { - text: { - value: [ - { text: '' }, - { text: '' }, - { text: ' ➡️ ' }, - { bold: true, text: 'Fold' }, - { text: ' and ' }, - { bold: true, text: 'Unfold' }, - { - text: ' a task to simplify your list using the arrow on the right ⤵️', - }, - ], - }, - numberType: 'type1', - collapsed: { value: false }, - }, - }, - ], - }, - blog: { - type: 'group', - properties: {}, - blocks: [ - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'As a collaborative real-time editor, AFFiNE aims to resolve problems in three situations:', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'bullet', - properties: { - text: { - value: [ - { - text: 'Multi-master replication: the synchronization of data between equipment and applications;', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'bullet', - properties: { - text: { - value: [ - { - text: 'Eventual consistency: the consistence of data regardless of network latency and outage;', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'bullet', - properties: { - text: { - value: [ - { - text: 'Conflict resolution: the resolution of conflict between simultaneous edits.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'To achieve these aims, a proper collaborative algorithm should be used. There are hundreds of collaborative algorithms being invented over the past three decades, but they usually fall into two categories: either operational transformation (OT) or conflict-free replicated data type (CRDT). We think CRDT is a better choice.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading1', - properties: { - text: { - value: [{ bold: true, text: 'What does CRDT do' }], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'CRDT is capable of discovering and resolving conflicts while ensuring the effective distribution and merge of date. It may sound like magic, but think about historical study, it is very possible to discover the truth from scattered evidence.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'For CRDT, every piece of data is like a "historic fragment". We keep collecting the fragments from other clients and then restore the truth by excluding repeated data and correcting false information.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading1', - properties: { - text: { - value: [{ bold: true, text: 'Why CRDT is better' }], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'In contrast to OT, CRDT possesses three big advantages:', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading2', - properties: { - text: { - value: [{ bold: true, text: 'Flexibility' }], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'CRDT supports more data types. For example, Yjs supports Array, Map, and Treelike, and therefore applies to more business scenarios.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading2', - properties: { - text: { - value: [{ bold: true, text: 'Performance' }], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'CRDT tolerates higher latency and can wait longer for solving conflicts, whereas the calculation of OT in the same condition may become too overwhelming for a server to sustain.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading2', - properties: { - text: { - value: [{ bold: true, text: 'Extensibility' }], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'Because CRDT supports more data types and editor elements, it is more extensible.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading1', - properties: { - text: { value: [{ text: 'Conclusion' }] }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { text: { value: [{ text: '' }] } }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { text: '' }, - { - type: 'link', - url: 'https://blog.affine.pro/', - id: 'link.stubssslo0rq', - children: [{ text: '← View all posts' }], - }, - { text: '' }, - ], - }, - }, - blocks: [], - }, - ], - }, - grid: { - type: 'group', - properties: {}, - blocks: [ - { - type: 'heading2', - properties: { - text: { - value: [ - { - bold: true, - text: 'Performance', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'CRDT tolerates higher latency and can wait longer for solving conflicts, whereas the calculation of OT in the same condition may become too overwhelming for a server to sustain.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading2', - properties: { - text: { - value: [ - { - bold: true, - text: 'Extensibility', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'Because CRDT supports more data types and editor elements, it is more extensible.', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'heading1', - properties: { - text: { - value: [ - { - text: 'Conclusion', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'grid', - properties: {}, - blocks: [ - { - type: 'gridItem', - properties: { - gridItemWidth: '50%', - }, - blocks: [ - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice.', - }, - ], - }, - }, - blocks: [], - }, - ], - }, - { - type: 'gridItem', - properties: { - gridItemWidth: '50%', - }, - blocks: [ - { - type: 'text', - properties: { - text: { - value: [ - { - text: 'Collaborative algorithm is still a foreign concept for many developers. There are some introductions to it, but as to how it shall be used, there is still lack of clear explanation. I hope this article helps. If you also work for a startup company and want some suggestions, CRDT, especially Yjs, should be a better choice.', - }, - ], - }, - }, - blocks: [], - }, - ], - }, - ], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: '', - }, - ], - }, - }, - blocks: [], - }, - { - type: 'text', - properties: { - text: { - value: [ - { - text: '', - }, - { - type: 'link', - url: 'https://blog.affine.pro/', - id: 'link.stubssslo0rq', - children: [ - { - text: '← View all posts', - }, - ], - }, - { - text: '', - }, - ], - }, - }, - }, - ], - }, -}; - -export type GroupTemplateKeys = 'todolist' | 'blog' | 'empty' | 'grid'; -export { groupTemplateMap }; diff --git a/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts b/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts index 3fa16ac460..953cf0e6de 100644 --- a/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts +++ b/libs/datasource/db-service/src/services/editor-block/templates/template-factory.ts @@ -1,6 +1,19 @@ -import { groupTemplateMap } from './group-templates'; -import { Template, TemplateMeta } from './types'; -const defaultTemplateList: Array = [ +import { Template, TemplateMeta, GroupTemplate } from './types'; +import blogTemplate from './blog.json'; +import emptyTemplate from './empty.json'; +import gridTemplate from './grid.json'; +import todoTemplate from './todo.json'; + +export type GroupTemplateKeys = 'todolist' | 'blog' | 'empty' | 'grid'; +type GroupTemplateMap = Record; +const groupTemplateMap = { + empty: emptyTemplate, + todolist: todoTemplate, + blog: blogTemplate, + grid: gridTemplate, +} as GroupTemplateMap; + +const defaultTemplateList = [ { name: 'New From Quick Start', groupKeys: ['todolist'], @@ -9,12 +22,13 @@ const defaultTemplateList: Array = [ { name: 'New From Blog', groupKeys: ['blog'] }, { name: ' New Todolist', groupKeys: ['todolist'] }, { name: ' New Empty Page', groupKeys: ['empty'] }, -]; +] as const; + const TemplateFactory = { defaultTemplateList: defaultTemplateList, generatePageTemplateByGroupKeys(props: TemplateMeta): Template { const newTitle = props.name || 'Get Started with AFFiNE'; - const keys = props.groupKeys || []; + const keys: GroupTemplateKeys[] = props.groupKeys || []; const blankPage: Template = { type: 'page', properties: { diff --git a/libs/datasource/db-service/src/services/editor-block/templates/todo.json b/libs/datasource/db-service/src/services/editor-block/templates/todo.json new file mode 100644 index 0000000000..a6cbbee0e3 --- /dev/null +++ b/libs/datasource/db-service/src/services/editor-block/templates/todo.json @@ -0,0 +1,173 @@ +{ + "type": "group", + "properties": {}, + "blocks": [ + { + "type": "heading1", + "properties": { + "text": { + "value": [{ "text": "🎓Graduating from the project" }] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "Congratulations! Now you can start create your own projects!" + } + ] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "To start using workspaces and folders hit the " + }, + { + "bold": true, + "text": "button at the top left of the screen" + } + ] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "" + }, + { + "text": "At any time if you feel lost do visit our 📚Blog: " + }, + { + "type": "link", + "url": "https://blog.affine.pro/", + "id": "link.qx4yhw81or54", + "children": [ + { + "text": "https://blog.affine.pro" + } + ] + }, + { + "text": "" + } + ] + }, + "collapsed": { + "value": false + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { + "text": "" + }, + { + "text": "If you have any suggestions drop a post in our Reddit Channel:" + }, + { + "type": "link", + "url": "https://www.reddit.com/r/Affine/", + "id": "link.zeafc4ogfvrb", + "children": [ + { + "text": "https://www.reddit.com/r/Affine/" + } + ] + }, + { + "text": " " + } + ] + } + }, + "blocks": [] + }, + { + "type": "heading2", + "properties": { + "text": { + "value": [ + { + "text": "🎉 The Essentials. Check things off after you tried them!" + } + ] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": " ✅ " }, + { "bold": true, "text": "Check" }, + { + "text": " the text box here to complete the task!" + } + ] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": "" }, + { "text": "" }, + { "text": " 👋 " }, + { "bold": true, "text": "Drag" }, + { + "text": " the ⠟ button left of the checkbox to reorder tasks" + }, + { "text": "" }, + { "text": "" } + ] + } + }, + "blocks": [] + }, + { + "type": "todo", + "properties": { + "text": { + "value": [ + { "text": "" }, + { "text": "" }, + { "text": " ➡️ " }, + { "bold": true, "text": "Fold" }, + { "text": " and " }, + { "bold": true, "text": "Unfold" }, + { + "text": " a task to simplify your list using the arrow on the right ⤵️" + } + ] + }, + "numberType": "type1", + "collapsed": { "value": false } + } + } + ] +} diff --git a/libs/datasource/db-service/src/services/editor-block/templates/types.ts b/libs/datasource/db-service/src/services/editor-block/templates/types.ts index 54b680adfc..5af9abf45d 100644 --- a/libs/datasource/db-service/src/services/editor-block/templates/types.ts +++ b/libs/datasource/db-service/src/services/editor-block/templates/types.ts @@ -1,5 +1,5 @@ -import { DefaultColumnsValue, BlockFlavorKeys } from './../index'; -import { groupTemplateMap, GroupTemplateKeys } from './group-templates'; +import { BlockFlavorKeys, DefaultColumnsValue } from './../index'; +import { GroupTemplateKeys } from './template-factory'; // interface Block { // type: BlockFlavorKeys; diff --git a/libs/datasource/db-service/src/services/workspace/user-config.ts b/libs/datasource/db-service/src/services/workspace/user-config.ts index 0977ee9092..0a5dca7eef 100644 --- a/libs/datasource/db-service/src/services/workspace/user-config.ts +++ b/libs/datasource/db-service/src/services/workspace/user-config.ts @@ -112,11 +112,15 @@ export class UserConfig extends ServiceBaseClass { async getWorkspaceName(workspace: string): Promise { const workspace_db_block = await this.getWorkspaceDbBlock(workspace); const workspaceName = - workspace_db_block.getDecoration(WORKSPACE_CONFIG) || - workspace_db_block.id; + workspace_db_block.getDecoration(WORKSPACE_CONFIG) || ''; return workspaceName; } + async getWorkspaceId(workspace: string): Promise { + const workspace_db_block = await this.getWorkspaceDbBlock(workspace); + return workspace_db_block.id; + } + async setWorkspaceName(workspace: string, workspaceName: string) { const workspace_db_block = await this.getWorkspaceDbBlock(workspace); workspace_db_block.setDecoration(WORKSPACE_CONFIG, workspaceName); diff --git a/libs/datasource/jwst/Cargo.toml b/libs/datasource/jwst/Cargo.toml new file mode 100644 index 0000000000..97a9345f9f --- /dev/null +++ b/libs/datasource/jwst/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "jwst" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/libs/datasource/jwst/src/lib.rs b/libs/datasource/jwst/src/lib.rs new file mode 100644 index 0000000000..6dad9e8b9d --- /dev/null +++ b/libs/datasource/jwst/src/lib.rs @@ -0,0 +1,2 @@ +// Open Source version coming soon +// the rust version of jwt diff --git a/libs/datasource/state/src/page.ts b/libs/datasource/state/src/page.ts index 39ed1b0ab9..b296d2824e 100644 --- a/libs/datasource/state/src/page.ts +++ b/libs/datasource/state/src/page.ts @@ -26,3 +26,13 @@ export const useCurrentEditors = () => { setCurrentEditors, }; }; + +// when first time transfer doc to board, need init the editor shape size to page size. +const _pageClientWidth = atom(1020); +export const usePageClientWidth = () => { + const [pageClientWidth, setPageClientWidth] = useAtom(_pageClientWidth); + return { + pageClientWidth, + setPageClientWidth, + }; +}; diff --git a/package.json b/package.json index 6cba0f24ad..ed3aca9478 100644 --- a/package.json +++ b/package.json @@ -7,9 +7,11 @@ "start": "env-cmd -f .env.local-dev nx serve ligo-virgo", "start:affine": "nx serve ligo-virgo", "start:keck": "nx serve keck", + "start:venus": "nx serve venus", "build": "nx build ligo-virgo", "build:local": "env-cmd -f .env.local nx build ligo-virgo", "build:keck": "nx build keck", + "build:venus": "nx build venus", "build:analytic": "cross-env BUNDLE_ANALYZER=true nx build --skip-nx-cache", "test": "nx run-many --target test --all", "check": "nx run-many --target check --all", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 529cd6f4a6..0121c3f933 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,23 @@ importers: mini-css-extract-plugin: 2.6.1_webpack@5.73.0 webpack: 5.73.0 + apps/venus: + specifiers: + '@emotion/react': ^11.10.0 + '@emotion/styled': ^11.10.0 + '@mui/joy': ^5.0.0-alpha.39 + lozad: ^1.16.0 + mini-css-extract-plugin: ^2.6.1 + webpack: ^5.73.0 + dependencies: + '@emotion/react': 11.10.0 + '@emotion/styled': 11.10.0_@emotion+react@11.10.0 + '@mui/joy': 5.0.0-alpha.39_72v32ofbtgpmxm7mhvtx474vfu + lozad: 1.16.0 + devDependencies: + mini-css-extract-plugin: 2.6.1_webpack@5.73.0 + webpack: 5.73.0 + libs/components/account: specifiers: '@authing/react-ui-components': ^3.1.23 @@ -334,7 +351,7 @@ importers: '@emotion/styled': 11.9.3_@emotion+react@11.9.3 '@mui/icons-material': 5.8.4_@mui+material@5.8.7 '@mui/material': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i - '@mui/x-data-grid': 5.12.3_lc5g5arqxncq7hlh6olwtqg42u + '@mui/x-data-grid': 5.12.3_7ff6pt5vb3e5jymp4h3bl3mztq is-hotkey: 0.2.0 is-url: 1.2.4 slate: 0.81.1 @@ -2688,6 +2705,28 @@ packages: tslib: 2.4.0 dev: false + /@emotion/babel-plugin/11.10.0: + resolution: {integrity: sha512-xVnpDAAbtxL1dsuSelU5A7BnY/lftws0wUexNJZTPsvX/1tM4GZJbclgODhvW4E+NH7E5VFcH0bBn30NvniPJA==} + peerDependencies: + '@babel/core': ^7.0.0 + peerDependenciesMeta: + '@babel/core': + optional: true + dependencies: + '@babel/helper-module-imports': 7.18.6 + '@babel/plugin-syntax-jsx': 7.18.6 + '@babel/runtime': 7.18.6 + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/serialize': 1.1.0 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.8.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.0.13 + dev: false + /@emotion/babel-plugin/11.9.2: resolution: {integrity: sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==} peerDependencies: @@ -2733,6 +2772,16 @@ packages: stylis: 4.0.13 dev: false + /@emotion/cache/11.10.1: + resolution: {integrity: sha512-uZTj3Yz5D69GE25iFZcIQtibnVCFsc/6+XIozyL3ycgWvEdif2uEw9wlUt6umjLr4Keg9K6xRPHmD8LGi+6p1A==} + dependencies: + '@emotion/memoize': 0.8.0 + '@emotion/sheet': 1.2.0 + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + stylis: 4.0.13 + dev: false + /@emotion/cache/11.9.3: resolution: {integrity: sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg==} dependencies: @@ -2747,16 +2796,53 @@ packages: resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} dev: false + /@emotion/hash/0.9.0: + resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} + dev: false + /@emotion/is-prop-valid/1.1.3: resolution: {integrity: sha512-RFg04p6C+1uO19uG8N+vqanzKqiM9eeV1LDOG3bmkYmuOj7NbKNlFC/4EZq5gnwAIlcC/jOT24f8Td0iax2SXA==} dependencies: '@emotion/memoize': 0.7.5 dev: false + /@emotion/is-prop-valid/1.2.0: + resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} + dependencies: + '@emotion/memoize': 0.8.0 + dev: false + /@emotion/memoize/0.7.5: resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==} dev: false + /@emotion/memoize/0.8.0: + resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} + dev: false + + /@emotion/react/11.10.0: + resolution: {integrity: sha512-K6z9zlHxxBXwN8TcpwBKcEsBsOw4JWCCmR+BeeOWgqp8GIU1yA2Odd41bwdAAr0ssbQrbJbVnndvv7oiv1bZeQ==} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@types/react': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/babel-plugin': 11.10.0 + '@emotion/cache': 11.10.1 + '@emotion/serialize': 1.1.0 + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + hoist-non-react-statics: 3.3.2 + dev: false + /@emotion/react/11.9.3: resolution: {integrity: sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ==} peerDependencies: @@ -2840,10 +2926,49 @@ packages: csstype: 3.1.0 dev: false + /@emotion/serialize/1.1.0: + resolution: {integrity: sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==} + dependencies: + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/unitless': 0.8.0 + '@emotion/utils': 1.2.0 + csstype: 3.1.0 + dev: false + /@emotion/sheet/1.1.1: resolution: {integrity: sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA==} dev: false + /@emotion/sheet/1.2.0: + resolution: {integrity: sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==} + dev: false + + /@emotion/styled/11.10.0_@emotion+react@11.10.0: + resolution: {integrity: sha512-V9oaEH6V4KePeQpgUE83i8ht+4Ri3E8Djp/ZPJ4DQlqWhSKITvgzlR3/YQE2hdfP4Jw3qVRkANJz01LLqK9/TA==} + peerDependencies: + '@babel/core': ^7.0.0 + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@emotion/react': + optional: true + '@types/react': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/babel-plugin': 11.10.0 + '@emotion/is-prop-valid': 1.2.0 + '@emotion/react': 11.10.0 + '@emotion/serialize': 1.1.0 + '@emotion/utils': 1.2.0 + dev: false + /@emotion/styled/11.9.3: resolution: {integrity: sha512-o3sBNwbtoVz9v7WB1/Y/AmXl69YHmei2mrVnK7JgyBJ//Rst5yqPZCecEJlMlJrFeWHp+ki/54uN265V2pEcXA==} peerDependencies: @@ -2951,14 +3076,26 @@ packages: resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} dev: false + /@emotion/unitless/0.8.0: + resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} + dev: false + /@emotion/utils/1.1.0: resolution: {integrity: sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==} dev: false + /@emotion/utils/1.2.0: + resolution: {integrity: sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==} + dev: false + /@emotion/weak-memoize/0.2.5: resolution: {integrity: sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==} dev: false + /@emotion/weak-memoize/0.3.0: + resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} + dev: false + /@eslint/eslintrc/1.3.0: resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4186,6 +4323,31 @@ packages: react-is: 17.0.2 dev: false + /@mui/base/5.0.0-alpha.92: + resolution: {integrity: sha512-ZgnSLrTXL4iUdLQhjp01dAOTQPQlnwrqjZRwDT3E6LZXEYn6cMv1MY6LZkWcF/zxrUnyasnsyMAgZ5d8AXS7bA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/is-prop-valid': 1.2.0 + '@mui/types': 7.1.5 + '@mui/utils': 5.9.3 + '@popperjs/core': 2.11.5 + clsx: 1.2.1 + prop-types: 15.8.1 + react-is: 18.2.0 + dev: false + /@mui/icons-material/5.8.4: resolution: {integrity: sha512-9Z/vyj2szvEhGWDvb+gG875bOGm8b8rlHBKOD1+nA3PcgC3fV6W1AU6pfOorPeBfH2X4mb9Boe97vHvaSndQvA==} engines: {node: '>=12.0.0'} @@ -4244,6 +4406,40 @@ packages: react: 18.2.0 dev: false + /@mui/joy/5.0.0-alpha.39_72v32ofbtgpmxm7mhvtx474vfu: + resolution: {integrity: sha512-F/cjEwvH9UFxIRJ30P8fuGOEDtgDBJCd++yTq8JYXARGCSlUMtbpijkPvnYFz69j3BtHCDhSaz3JA0cxcwVjaQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + react: + optional: true + react-dom: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/react': 11.10.0 + '@emotion/styled': 11.10.0_@emotion+react@11.10.0 + '@mui/base': 5.0.0-alpha.92 + '@mui/system': 5.9.3_72v32ofbtgpmxm7mhvtx474vfu + '@mui/types': 7.1.5 + '@mui/utils': 5.9.3 + clsx: 1.2.1 + csstype: 3.1.0 + prop-types: 15.8.1 + react-is: 18.2.0 + dev: false + /@mui/material/5.8.7_d6menda4vqwq6peqnkbe7mkj4i: resolution: {integrity: sha512-Oo62UhrgEi+BMLr3nUEASJgScE2/hhq14CbBUmrVV3GQlEGtqMZsy26Vb0AqEmphFeN3TXlsbM9aeW5yq8ZFlw==} engines: {node: '>=12.0.0'} @@ -4355,6 +4551,46 @@ packages: react: 18.2.0 dev: false + /@mui/private-theming/5.9.3: + resolution: {integrity: sha512-Ys3WO39WqoGciGX9k5AIi/k2zJhlydv4FzlEEwtw9OqdMaV0ydK/TdZekKzjP9sTI/JcdAP3H5DWtUaPLQJjWg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@mui/utils': 5.9.3 + prop-types: 15.8.1 + dev: false + + /@mui/styled-engine/5.8.7_72v32ofbtgpmxm7mhvtx474vfu: + resolution: {integrity: sha512-tVqtowjbYmiRq+qcqXK731L9eWoL9H8xTRhuTgaDGKdch1zlt4I2UwInUe1w2N9N/u3/jHsFbLcl1Un3uOwpQg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/cache': 11.9.3 + '@emotion/react': 11.10.0 + '@emotion/styled': 11.10.0_@emotion+react@11.10.0 + csstype: 3.1.0 + prop-types: 15.8.1 + dev: false + /@mui/styled-engine/5.8.7_d6menda4vqwq6peqnkbe7mkj4i: resolution: {integrity: sha512-tVqtowjbYmiRq+qcqXK731L9eWoL9H8xTRhuTgaDGKdch1zlt4I2UwInUe1w2N9N/u3/jHsFbLcl1Un3uOwpQg==} engines: {node: '>=12.0.0'} @@ -4372,8 +4608,8 @@ packages: dependencies: '@babel/runtime': 7.18.6 '@emotion/cache': 11.9.3 - '@emotion/react': 11.9.3_@babel+core@7.18.6 - '@emotion/styled': 11.9.3_dc5dh2wp562rsjxvguwi2i3yzq + '@emotion/react': 11.9.3 + '@emotion/styled': 11.9.3_@emotion+react@11.9.3 csstype: 3.1.0 prop-types: 15.8.1 dev: false @@ -4453,8 +4689,8 @@ packages: optional: true dependencies: '@babel/runtime': 7.18.6 - '@emotion/react': 11.9.3 - '@emotion/styled': 11.9.3_@emotion+react@11.9.3 + '@emotion/react': 11.9.3_@babel+core@7.18.6 + '@emotion/styled': 11.9.3_dc5dh2wp562rsjxvguwi2i3yzq '@mui/private-theming': 5.8.6 '@mui/styled-engine': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i '@mui/types': 7.1.4 @@ -4464,6 +4700,66 @@ packages: prop-types: 15.8.1 dev: false + /@mui/system/5.9.3_72v32ofbtgpmxm7mhvtx474vfu: + resolution: {integrity: sha512-EXQV2POwncstHLYII+G4VSYdEFun1TjBbQSBDK76DbIkug8nPjtjAZ+3Kgk3/NoFIigW+vQ9cDVUZtlbRH6YMQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/react': 11.10.0 + '@emotion/styled': 11.10.0_@emotion+react@11.10.0 + '@mui/private-theming': 5.9.3 + '@mui/styled-engine': 5.8.7_72v32ofbtgpmxm7mhvtx474vfu + '@mui/types': 7.1.5 + '@mui/utils': 5.9.3 + clsx: 1.2.1 + csstype: 3.1.0 + prop-types: 15.8.1 + dev: false + + /@mui/system/5.9.3_d6menda4vqwq6peqnkbe7mkj4i: + resolution: {integrity: sha512-EXQV2POwncstHLYII+G4VSYdEFun1TjBbQSBDK76DbIkug8nPjtjAZ+3Kgk3/NoFIigW+vQ9cDVUZtlbRH6YMQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@emotion/react': 11.9.3 + '@emotion/styled': 11.9.3_@emotion+react@11.9.3 + '@mui/private-theming': 5.9.3 + '@mui/styled-engine': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i + '@mui/types': 7.1.5 + '@mui/utils': 5.9.3 + clsx: 1.2.1 + csstype: 3.1.0 + prop-types: 15.8.1 + dev: false + /@mui/types/7.1.4: resolution: {integrity: sha512-uveM3byMbthO+6tXZ1n2zm0W3uJCQYtwt/v5zV5I77v2v18u0ITkb8xwhsDD2i3V2Kye7SaNR6FFJ6lMuY/WqQ==} peerDependencies: @@ -4484,6 +4780,15 @@ packages: '@types/react': 18.0.14 dev: false + /@mui/types/7.1.5: + resolution: {integrity: sha512-HnRXrxgHJYJcT8ZDdDCQIlqk0s0skOKD7eWs9mJgBUu70hyW4iA6Kiv3yspJR474RFH8hysKR65VVSzUSzkuwA==} + peerDependencies: + '@types/react': '*' + peerDependenciesMeta: + '@types/react': + optional: true + dev: false + /@mui/utils/5.8.6: resolution: {integrity: sha512-QM2Sd1xZo2jOt2Vz5Rmro+pi2FLJyiv4+OjxkUwXR3oUM65KSMAMLl/KNYU55s3W3DLRFP5MVwE4FhAbHseHAg==} engines: {node: '>=12.0.0'} @@ -4517,7 +4822,23 @@ packages: react-is: 17.0.2 dev: false - /@mui/x-data-grid/5.12.3_lc5g5arqxncq7hlh6olwtqg42u: + /@mui/utils/5.9.3: + resolution: {integrity: sha512-l0N5bcrenE9hnwZ/jPecpIRqsDFHkPXoFUcmkgysaJwVZzJ3yQkGXB47eqmXX5yyGrSc6HksbbqXEaUya+siew==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + react: + optional: true + dependencies: + '@babel/runtime': 7.18.6 + '@types/prop-types': 15.7.5 + '@types/react-is': 17.0.3 + prop-types: 15.8.1 + react-is: 18.2.0 + dev: false + + /@mui/x-data-grid/5.12.3_7ff6pt5vb3e5jymp4h3bl3mztq: resolution: {integrity: sha512-57A2MkRR/uUNC/dECFV0YDJvi1Q+gQgmgw1OHmZ1uSnKh29PcHpswkdapO0LueLpxAy8tfH+fTtnnPDmYgJeUg==} engines: {node: '>=12.0.0'} peerDependencies: @@ -4535,7 +4856,7 @@ packages: dependencies: '@babel/runtime': 7.18.6 '@mui/material': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i - '@mui/system': 5.8.7_d6menda4vqwq6peqnkbe7mkj4i + '@mui/system': 5.9.3_d6menda4vqwq6peqnkbe7mkj4i '@mui/utils': 5.8.6 clsx: 1.2.0 prop-types: 15.8.1 @@ -7537,6 +7858,15 @@ packages: cosmiconfig: 6.0.0 resolve: 1.22.1 + /babel-plugin-macros/3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.18.6 + cosmiconfig: 7.0.1 + resolve: 1.22.1 + dev: false + /babel-plugin-open-source/1.3.4: resolution: {integrity: sha512-7lsfY30y/XYYbK4vYHfPyC/aF2KM0vz2OFaz6+tDuAg6A3223is7vqzFYd0va7n/YTw5osScevR0bQ4hO8gPQg==} dependencies: @@ -8362,7 +8692,6 @@ packages: parse-json: 5.2.0 path-type: 4.0.0 yaml: 1.10.2 - dev: true /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -12777,6 +13106,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /lozad/1.16.0: + resolution: {integrity: sha512-JBr9WjvEFeKoyim3svo/gsQPTkgG/mOHJmDctZ/+U9H3ymUuvEkqpn8bdQMFsvTMcyRJrdJkLv0bXqGm0sP72w==} + dev: false + /lru-cache/4.0.2: resolution: {integrity: sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==} dependencies: @@ -15204,7 +15537,6 @@ packages: /react-is/18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - dev: true /react-list/0.8.17: resolution: {integrity: sha512-pgmzGi0G5uGrdHzMhgO7KR1wx5ZXVvI3SsJUmkblSAKtewIhMwbQiMuQiTE83ozo04BQJbe0r3WIWzSO0dR1xg==} diff --git a/tsconfig.base.json b/tsconfig.base.json index 261d748949..5a45715ab6 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,7 @@ "skipLibCheck": true, "skipDefaultLibCheck": true, "baseUrl": ".", + "resolveJsonModule": true, "paths": { "@toeverything/components/account": [ "libs/components/account/src/index.ts" diff --git a/workspace.json b/workspace.json index c221a46c1d..4bc8d78e14 100644 --- a/workspace.json +++ b/workspace.json @@ -28,6 +28,7 @@ "framework-virgo": "libs/framework/virgo", "keck": "apps/keck", "ligo-virgo": "apps/ligo-virgo", - "utils": "libs/utils" + "utils": "libs/utils", + "venus": "apps/venus" } }