diff --git a/.all-contributorsrc b/.all-contributorsrc index 0340f41c92..3286d1be28 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1,5 +1,5 @@ { - "projectName": "Ligo-Virgo", + "projectName": "toeverything", "projectOwner": "toeverything", "repoType": "github", "repoHost": "https://github.com", @@ -25,7 +25,7 @@ "login": "tzhangchi", "name": "Chi Zhang", "avatar_url": "https://avatars.githubusercontent.com/u/5910926?v=4", - "profile": "https://zhangchi.blog.csdn.net/", + "profile": "http://zhangchi.page/", "contributions": [ "code", "doc" @@ -33,7 +33,7 @@ }, { "login": "alt1o", - "name": "alt1o", + "name": "wang xinglong", "avatar_url": "https://avatars.githubusercontent.com/u/21084335?v=4", "profile": "https://github.com/alt1o", "contributions": [ @@ -43,7 +43,7 @@ }, { "login": "DiamondThree", - "name": "Diamond", + "name": "DiamondThree", "avatar_url": "https://avatars.githubusercontent.com/u/24630517?v=4", "profile": "https://github.com/DiamondThree", "contributions": [ @@ -63,7 +63,7 @@ }, { "login": "zuoxiaodong0815", - "name": "zuoxiaodong0815", + "name": "xiaodong zuo", "avatar_url": "https://avatars.githubusercontent.com/u/53252747?v=4", "profile": "https://github.com/zuoxiaodong0815", "contributions": [ @@ -73,7 +73,7 @@ }, { "login": "SaikaSakura", - "name": "SaikaSakura", + "name": "MingLIang Wang", "avatar_url": "https://avatars.githubusercontent.com/u/11530942?v=4", "profile": "https://github.com/SaikaSakura", "contributions": [ @@ -92,10 +92,10 @@ ] }, { - "login": "tuluffy", - "name": "tuluffy", - "avatar_url": "https://avatars.githubusercontent.com/u/26808339?v=4", - "profile": "https://tuluffy.github.io/angular.github.io/", + "login": "mitsuhatu", + "name": "mitsuhatu", + "avatar_url": "https://avatars.githubusercontent.com/u/110213079?v=4", + "profile": "https://github.com/mitsuhatu", "contributions": [ "code", "doc" diff --git a/.github/deployment/Caddyfile-affine b/.github/deployment/Caddyfile-affine new file mode 100644 index 0000000000..b6db6009e9 --- /dev/null +++ b/.github/deployment/Caddyfile-affine @@ -0,0 +1,28 @@ +:3000 { + 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/Caddyfile b/.github/deployment/Caddyfile-lisa similarity index 100% rename from Caddyfile rename to .github/deployment/Caddyfile-lisa diff --git a/Caddyfile-venus b/.github/deployment/Caddyfile-venus similarity index 100% rename from Caddyfile-venus rename to .github/deployment/Caddyfile-venus diff --git a/.github/deployment/Dockerfile-affine b/.github/deployment/Dockerfile-affine new file mode 100644 index 0000000000..46a46233f2 --- /dev/null +++ b/.github/deployment/Dockerfile-affine @@ -0,0 +1,21 @@ +FROM node:16-alpine as builder +WORKDIR /app +COPY . . +RUN apk add g++ make python3 git libpng-dev +RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:local + +FROM node:16-alpine as relocate +WORKDIR /app +COPY --from=builder /app/dist/apps/ligo-virgo ./dist +COPY --from=builder /app/.github/deployment/Caddyfile-affine ./Caddyfile +RUN rm ./dist/*.txt + +# ============= +# AFFiNE image +# ============= +FROM caddy:2.4.6-alpine as AFFiNE +WORKDIR /app +COPY --from=relocate /app . + +EXPOSE 3000 +CMD ["caddy", "run"] \ No newline at end of file diff --git a/Dockerfile-keck b/.github/deployment/Dockerfile-keck similarity index 93% rename from Dockerfile-keck rename to .github/deployment/Dockerfile-keck index 598fded0ab..7918e49427 100644 --- a/Dockerfile-keck +++ b/.github/deployment/Dockerfile-keck @@ -1,7 +1,7 @@ FROM node:16-alpine as builder WORKDIR /app COPY . . -RUN apk add g++ make python3 git +RUN apk add g++ make python3 git libpng-dev RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:keck FROM node:16-alpine as node_modules diff --git a/Dockerfile b/.github/deployment/Dockerfile-lisa similarity index 78% rename from Dockerfile rename to .github/deployment/Dockerfile-lisa index f6e2249faf..471e4d0d48 100644 --- a/Dockerfile +++ b/.github/deployment/Dockerfile-lisa @@ -1,13 +1,13 @@ FROM node:16-alpine as builder WORKDIR /app COPY . . -RUN apk add g++ make python3 git +RUN apk add g++ make python3 git libpng-dev RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build FROM node:16-alpine as relocate WORKDIR /app COPY --from=builder /app/dist/apps/ligo-virgo ./dist -COPY --from=builder /app/Caddyfile ./ +COPY --from=builder /app/.github/deployment/Caddyfile-lisa ./Caddyfile RUN rm ./dist/*.txt # ============= diff --git a/Dockerfile-venus b/.github/deployment/Dockerfile-venus similarity index 78% rename from Dockerfile-venus rename to .github/deployment/Dockerfile-venus index 55ece303c6..585ade8a20 100644 --- a/Dockerfile-venus +++ b/.github/deployment/Dockerfile-venus @@ -1,13 +1,13 @@ FROM node:16-alpine as builder WORKDIR /app COPY . . -RUN apk add g++ make python3 git +RUN apk add g++ make python3 git libpng-dev RUN npm i -g pnpm@7 && pnpm i --frozen-lockfile --store=node_modules/.pnpm-store && pnpm run build:venus FROM node:16-alpine as relocate WORKDIR /app COPY --from=builder /app/dist/apps/venus ./dist -COPY --from=builder /app/Caddyfile-venus ./Caddyfile +COPY --from=builder /app/.github/deployment/Caddyfile-venus ./Caddyfile RUN rm ./dist/*.txt # ============= diff --git a/.github/workflows/affine.yml b/.github/workflows/affine.yml new file mode 100644 index 0000000000..28ca2d429b --- /dev/null +++ b/.github/workflows/affine.yml @@ -0,0 +1,57 @@ +name: Build AFFiNE-Local + +on: + push: + branches: [master] + pull_request: + branches: [master] + +# 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 + AFFINE_IMAGE_NAME: AFFiNE + 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 (AFFiNE-Local) + id: meta_affine + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.AFFINE_IMAGE_NAME }} + tags: ${{ env.IMAGE_TAG_LATEST }} + + - name: Build and push Docker image (AFFINE-Local) + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + file: ./.github/deployment/Dockerfile-affine + push: ${{ github.ref == 'refs/heads/master' && true || false }} + tags: ${{ steps.meta_affine.outputs.tags }} + labels: ${{ steps.meta_affine.outputs.labels }} + target: AFFiNE diff --git a/.github/workflows/keck.yml b/.github/workflows/keck.yml index d7b915c100..a09d790455 100644 --- a/.github/workflows/keck.yml +++ b/.github/workflows/keck.yml @@ -6,11 +6,13 @@ on: branches: [master] paths: - 'apps/keck/**' + - '.github/deployment' - '.github/workflows/keck.yml' pull_request: branches: [master] paths: - 'apps/keck/**' + - '.github/deployment' - '.github/workflows/keck.yml' # Cancels all previous workflow runs for pull requests that have not completed. @@ -60,7 +62,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: ./Dockerfile-keck + file: ./.github/deployment/Dockerfile-keck push: ${{ github.ref == 'refs/heads/field' && true || false }} tags: ${{ steps.meta_keck.outputs.tags }} labels: ${{ steps.meta_keck.outputs.labels }} diff --git a/.github/workflows/lisa.yml b/.github/workflows/lisa.yml index 878fad6274..ead4c8fff2 100644 --- a/.github/workflows/lisa.yml +++ b/.github/workflows/lisa.yml @@ -53,6 +53,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . + file: ./.github/deployment/Dockerfile-lisa push: ${{ github.ref == 'refs/heads/master' && true || false }} tags: ${{ steps.meta_lisa.outputs.tags }} labels: ${{ steps.meta_lisa.outputs.labels }} diff --git a/.github/workflows/venus.yml b/.github/workflows/venus.yml index 59bf758cdc..8b8841404c 100644 --- a/.github/workflows/venus.yml +++ b/.github/workflows/venus.yml @@ -5,6 +5,7 @@ on: branches: [master] paths: - 'apps/venus/**' + - '.github/deployment' - '.github/workflows/venus.yml' pull_request: branches: [master] @@ -59,7 +60,7 @@ jobs: uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc with: context: . - file: ./Dockerfile-venus + file: ./.github/deployment/Dockerfile-venus push: ${{ github.ref == 'refs/heads/master' && true || false }} tags: ${{ steps.meta_venus.outputs.tags }} labels: ${{ steps.meta_venus.outputs.labels }} diff --git a/README.md b/README.md index 0fbcf5bf80..26d85580e3 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Planning, Sorting and Creating all Together. Open-source, Privacy-First, and Fre @@ -38,21 +38,29 @@ See https://github.com/all-contributors/all-contributors/issues/361#issuecomment Telegram

-

affine_screen

+

affine_screen

-# Stay Up-to-Date +# Stay Up-to-Date and Support Us ![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif) +# How to use + +If you have experience in front-end development, please [refer to here](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine); if you want to experience our latest version, please wait a moment, we will launch a web version in the near future. +And, thanks to Lee who [made a desktop build with Tauri](https://github.com/m1911star/affine-client) for you to try out. +Please notice that AFFiNE is still under Alpha stage and is not ready for production use. + # Table of contents -- [Stay Up-to-Date](#stay-up-to-date) +- [Stay Up-to-Date and Support Us](#stay-up-to-date-and-support-us) +- [How to Use](#how-to-use) - [Table of contents](#table-of-contents) - [Shape your page](#shape-your-page) - [Plan your task](#plan-your-task) - [Sort your knowledge](#sort-your-knowledge) - [Create your story](#create-your-story) -- [Getting Started with development](#getting-started-with-development) +- [Documentation](#documentation) + - [Getting Started with development](#getting-started-with-development) - [Roadmap](#roadmap) - [Releases](#releases) - [Feature requests](#feature-requests) @@ -60,6 +68,7 @@ See https://github.com/all-contributors/all-contributors/issues/361#issuecomment - [The Philosophy of AFFiNE](#the-philosophy-of-affine) - [Community](#community) - [Contributors](#contributors) +- [Acknowledgments](#acknowledgments) - [License](#license) ## Shape your page @@ -80,9 +89,13 @@ We want your data always to be yours, and we don't want to make any sacrifice to 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 +# Documentation -Please view the [documentation](https://affine.gitbook.io/affine/) in Contribute-to-AFFiNE/Software-Contributions/Environment-setup. +AFFiNE is not yet ready for production use. To install, you may check how to build or depoly the AFFiNE in [quick-start](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine/quick-start). For the full documentation, please view it [here](https://affine.gitbook.io/affine/). + +## Getting Started with development + +Please view the path Contribute-to-AFFiNE/Software-Contributions/Quick-Start in documentation. # Roadmap @@ -110,7 +123,7 @@ It is all perfect... If there are not so many waste operations and redundant inf That's why we are making AFFiNE. Some of the most important features are: - Transformable - - Every block can be transformed equally as a database + - Every block can be transformed equally well as a database - e.g. you can now set up a to-do with MarkDown in text view and edit it in kanban view. - Every doc can be turned into a whiteboard - 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. @@ -134,9 +147,17 @@ We would like to give special thanks to the innovators and pioneers who greatly We would also like to give thanks to open-source projects that make affine possible: -- Yjs & Yrs -- React -- Rust +- [Yjs](https://github.com/yjs/yjs) & [Yrs](https://github.com/y-crdt/y-crdt) -- Fundamental support of CRDTs for our implements on state management and data sync. +- [React](https://github.com/facebook/react) -- View layer support and web GUI framework. +- [Rust](https://github.com/rust-lang/rust) -- High performance language that extends the ability and availability of our real-time backend, JWST. +- [Fossil](https://www2.fossil-scm.org/home/doc/trunk/www/index.wiki) -- Source code management tool made with CRDTs which inspired our design on block data structure. +- [slatejs](https://github.com/ianstormtaylor/slate) -- Customizable rich-text editor. +- [Jotai](https://github.com/pmndrs/jotai) -- Minimal state management tool for frontend. +- [Tldraw](https://github.com/tldraw/tldraw) -- Excellent drawing board. +- [MUI](https://github.com/mui/material-ui) -- Our most used graphic UI component library. +- Other [dependancies](https://github.com/toeverything/AFFiNE/network/dependencies) + +Thanks a lot to the community for providing such powerful and simple libraries, so that we can focus more on the implementation of the product logic, and we hope that in the future our projects will also provide a more easy-to-use knowledge base for everyone. # Community @@ -152,16 +173,16 @@ For help, discussion about best practices, or any other conversation that would - - - + + + - - + + - + diff --git a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx index b2ebc5f7dc..76c48efeb6 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/Page.tsx @@ -1,16 +1,9 @@ /* eslint-disable filename-rules/match */ -import { - useEffect, - useRef, - type UIEvent, - useState, - useLayoutEffect, -} from 'react'; +import { useEffect, useRef, type UIEvent, useState } from 'react'; import { useParams } from 'react-router'; import { MuiBox as Box, MuiCircularProgress as CircularProgress, - MuiDivider as Divider, styled, } from '@toeverything/components/ui'; import { AffineEditor } from '@toeverything/components/affine-editor'; @@ -31,7 +24,7 @@ import { WorkspaceName } from './workspace-name'; import { CollapsiblePageTree } from './collapsible-page-tree'; import { useFlag } from '@toeverything/datasource/feature-flags'; import { type BlockEditor } from '@toeverything/components/editor-core'; - +import { Tabs } from './components/tabs'; type PageProps = { workspace: string; }; @@ -40,30 +33,8 @@ export function Page(props: PageProps) { const { page_id } = useParams(); const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } = useShowSpaceSidebar(); - const { user } = useUserAndSpaces(); const dailyNotesFlag = useFlag('BooleanDailyNotes', false); - useEffect(() => { - if (!user?.id || !page_id) return; - const updateRecentPages = async () => { - // TODO: deal with it temporarily - await services.api.editorBlock.getWorkspaceDbBlock( - props.workspace, - { - userId: user.id, - } - ); - - // await services.api.userConfig.addRecentPage( - // props.workspace, - // user.id, - // page_id - // ); - await services.api.editorBlock.clearUndoRedo(props.workspace); - }; - updateRecentPages(); - }, [user, props.workspace, page_id]); - return ( @@ -80,7 +51,9 @@ export function Page(props: PageProps) { onMouseLeave={() => setSpaceSidebarVisible(false)} > - + + +
{dailyNotesFlag && ( @@ -92,14 +65,14 @@ export function Page(props: PageProps) { )}
- + {page_id ? : null}
@@ -120,38 +93,29 @@ const EditorContainer = ({ workspace: string; }) => { const [lockScroll, setLockScroll] = useState(false); - const scrollContainerRef = useRef(); + const [scrollContainer, setScrollContainer] = useState(); const editorRef = useRef(); + const onScroll = (event: UIEvent) => { editorRef.current.getHooks().onRootNodeScroll(event); editorRef.current.scrollManager.emitScrollEvent(event); }; - useEffect(() => { - editorRef.current.scrollManager.scrollContainer = - scrollContainerRef.current; - - editorRef.current.scrollManager.scrollController = { - lockScroll: () => setLockScroll(true), - unLockScroll: () => setLockScroll(false), - }; - }, []); - const { setPageClientWidth } = usePageClientWidth(); useEffect(() => { - if (scrollContainerRef.current) { + if (scrollContainer) { const obv = new ResizeObserver(e => { setPageClientWidth(e[0].contentRect.width); }); - obv.observe(scrollContainerRef.current); + obv.observe(scrollContainer); return () => obv.disconnect(); } - }); + }, [setPageClientWidth, scrollContainer]); return ( setScrollContainer(ref)} onScroll={onScroll} > {pageId ? ( @@ -159,6 +123,11 @@ const EditorContainer = ({ workspace={workspace} rootBlockId={pageId} ref={editorRef} + scrollContainer={scrollContainer} + scrollController={{ + lockScroll: () => setLockScroll(true), + unLockScroll: () => setLockScroll(false), + }} /> ) : ( {children ? ( diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx new file mode 100644 index 0000000000..8b33a276c4 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/Logo.tsx @@ -0,0 +1,19 @@ +export const Logo = ({ color, style, ...props }) => { + return ( + + + + + ); +}; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts new file mode 100644 index 0000000000..33af505338 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/logo/index.ts @@ -0,0 +1 @@ +export { Logo } from './Logo'; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx new file mode 100644 index 0000000000..3722c5e162 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/Tabs.tsx @@ -0,0 +1,61 @@ +import { useState } from 'react'; +import { MuiDivider as Divider, styled } from '@toeverything/components/ui'; +import type { ValueOf } from '@toeverything/utils'; + +const StyledTabs = styled('div')({ + width: '100%', + height: '12px', + marginTop: '12px', + display: 'flex', + alignItems: 'center', + cursor: 'pointer', +}); + +const StyledDivider = styled(Divider)<{ isActive?: boolean }>( + ({ isActive }) => { + return { + flex: 1, + backgroundColor: isActive ? '#3E6FDB' : '#ECF1FB', + borderWidth: '2px', + }; + } +); + +const TAB_TITLE = { + PAGES: 'pages', + GALLERY: 'gallery', + TOC: 'toc', +} as const; + +const TabMap = new Map([ + ['PAGES', 'pages'], + ['GALLERY', 'gallery'], + ['TOC', 'toc'], +]); + +type TabKey = keyof typeof TAB_TITLE; +type TabValue = ValueOf; + +const Tabs = () => { + const [activeTab, setActiveTab] = useState(TAB_TITLE.PAGES); + + const onClick = (v: TabValue) => { + setActiveTab(v); + }; + + return ( + + {[...TabMap.entries()].map(([k, v]) => { + return ( + onClick(v)} + /> + ); + })} + + ); +}; + +export { Tabs }; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts new file mode 100644 index 0000000000..941e6baba5 --- /dev/null +++ b/apps/ligo-virgo/src/pages/workspace/docs/components/tabs/index.ts @@ -0,0 +1 @@ +export { Tabs } from './Tabs'; diff --git a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx index 24d69b659a..cf6f3a0313 100644 --- a/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx +++ b/apps/ligo-virgo/src/pages/workspace/docs/workspace-name.tsx @@ -1,27 +1,28 @@ import { - MuiButton as Button, - Switch, styled, MuiOutlinedInput as OutlinedInput, } from '@toeverything/components/ui'; -import { LogoIcon } from '@toeverything/components/icons'; +import { PinIcon } from '@toeverything/components/icons'; import { useUserAndSpaces, useShowSpaceSidebar, } from '@toeverything/datasource/state'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { services } from '@toeverything/datasource/db-service'; +import { Logo } from './components/logo/Logo'; const WorkspaceContainer = styled('div')({ display: 'flex', - alignItems: 'center', - minHeight: 60, - padding: '12px 0px', + flexDirection: 'column', + paddingBottom: '12px', color: '#566B7D', }); const LeftContainer = styled('div')({ flex: 'auto', display: 'flex', + height: '52px', + alignItems: 'center', + margin: '0 12px', }); const LogoContainer = styled('div')({ @@ -32,20 +33,36 @@ const LogoContainer = styled('div')({ minWidth: 24, }); -const StyledLogoIcon = styled(LogoIcon)(({ theme }) => { - return { - color: theme.affine.palette.primary, - width: '16px !important', - height: '16px !important', - }; +const StyledPin = styled('div')({ + display: 'flex', + justifyContent: 'end', + alignItems: 'center', +}); + +const StyledWorkspace = styled('div')({ + height: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-between', + marginLeft: '12px', + paddingLeft: '12px', +}); + +const StyledWorkspaceDesc = styled('div')({ + fontSize: '12px', + color: '#98ACBD', + height: '18px', + + display: 'flex', + alignItems: 'center', }); const WorkspaceNameContainer = styled('div')({ display: 'flex', alignItems: 'center', flex: 'auto', - width: '100px', - marginRight: '10px', + width: '165px', + height: '34px', input: { padding: '5px 10px', }, @@ -58,21 +75,15 @@ const WorkspaceNameContainer = styled('div')({ }); const WorkspaceReNameContainer = styled('div')({ - marginRight: '10px', + height: '34px', + display: 'flex', + alignItems: 'center', + input: { padding: '5px 10px', }, }); -const ToggleDisplayContainer = styled('div')({ - display: 'flex', - alignItems: 'center', - fontSize: 12, - color: '#3E6FDB', - padding: 6, - minWidth: 64, -}); - export const WorkspaceName = () => { const { currentSpaceId } = useUserAndSpaces(); const { fixedDisplay, toggleSpaceSidebar } = useShowSpaceSidebar(); @@ -139,35 +150,46 @@ export const WorkspaceName = () => { return ( + + + - + - {inRename ? ( - - setInRename(false)} - /> - - ) : ( - - setInRename(true)}> - {workspaceName || workspaceId} - - - )} + + {inRename ? ( + + setInRename(false)} + /> + + ) : ( + + setInRename(true)}> + {workspaceName || workspaceId} + + + )} + + + To shape, Not to Adapt. + + - - - ); }; diff --git a/apps/venus/package.json b/apps/venus/package.json index 20c7374fdc..fe69da1b8e 100644 --- a/apps/venus/package.json +++ b/apps/venus/package.json @@ -14,6 +14,9 @@ }, "devDependencies": { "mini-css-extract-plugin": "^2.6.1", - "webpack": "^5.73.0" + "image-minimizer-webpack-plugin": "^3.2.3", + "imagemin": "^8.0.1", + "imagemin-optipng": "^8.0.0", + "webpack": "^5.74.0" } } diff --git a/apps/venus/src/app/collaboration.png b/apps/venus/src/app/collaboration.png new file mode 100644 index 0000000000..bad2daedde Binary files /dev/null and b/apps/venus/src/app/collaboration.png differ diff --git a/apps/venus/src/app/index.tsx b/apps/venus/src/app/index.tsx index 8e17108f13..dea6dc0b7b 100644 --- a/apps/venus/src/app/index.tsx +++ b/apps/venus/src/app/index.tsx @@ -6,13 +6,18 @@ 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'; +import LogoImage from './logo.png'; +import CollaborationImage from './collaboration.png'; +import PageImage from './page.png'; +import ShapeImage from './shape.png'; +import TaskImage from './task.png'; + const DiscordIcon = (props: any) => { return ( { width="71" height="55" viewBox="0 0 71 55" - fill="none" + fill="currentcolor" > @@ -407,10 +412,7 @@ export function App() { }, }} > - + @@ -515,14 +517,16 @@ export function App() { justifyContent: 'left', textAlign: 'left', transition: 'all .5s', - // boxShadow: '2px 2px 40px #08f2', - // ':hover': { - // boxShadow: '2px 2px 40px #08f4', - // }, + transform: 'scale(0.98)', + boxShadow: '2px 2px 40px #0002', + ':hover': { + transform: 'scale(1)', + boxShadow: '2px 2px 40px #0004', + }, }} > @@ -537,10 +541,10 @@ export function App() { }} > @@ -656,14 +662,14 @@ export function App() { justifyContent: 'center', margin: 'auto', transition: 'all .5s', - // boxShadow: '2px 2px 40px #08f2', - // ':hover': { - // boxShadow: '2px 2px 40px #08f4', - // }, + transform: 'scale(0.98)', + ':hover': { + transform: 'scale(1)', + }, }} > @@ -676,7 +682,7 @@ export function App() { margin: 'auto', }} > - + @@ -738,13 +744,20 @@ export function App() { }} > - + window.open( + 'https://github.com/toeverything/AFFiNE/' + ) + } > GitHub - + - + window.open('https://www.reddit.com/r/Affine/') + } > Reddit - + - + window.open('https://t.me/affineworkos') + } > Telegram - + - + window.open('https://discord.gg/yz6tGVsf5p') + } > @@ -883,12 +927,16 @@ export function App() { }} > Discord - + diff --git a/apps/venus/src/assets/logo.png b/apps/venus/src/app/logo.png similarity index 100% rename from apps/venus/src/assets/logo.png rename to apps/venus/src/app/logo.png diff --git a/apps/venus/src/app/page.png b/apps/venus/src/app/page.png new file mode 100644 index 0000000000..d5f393262b Binary files /dev/null and b/apps/venus/src/app/page.png differ diff --git a/apps/venus/src/app/shape.png b/apps/venus/src/app/shape.png new file mode 100644 index 0000000000..ad0ea8088c Binary files /dev/null and b/apps/venus/src/app/shape.png differ diff --git a/apps/venus/src/app/task.png b/apps/venus/src/app/task.png new file mode 100644 index 0000000000..279d968cb5 Binary files /dev/null and b/apps/venus/src/app/task.png differ diff --git a/apps/venus/src/assets/collaboration.png b/apps/venus/src/assets/collaboration.png deleted file mode 100644 index 5d56d61581..0000000000 Binary files a/apps/venus/src/assets/collaboration.png and /dev/null differ diff --git a/apps/venus/src/assets/discord.svg b/apps/venus/src/assets/discord.svg deleted file mode 100644 index 9da6ee0330..0000000000 --- a/apps/venus/src/assets/discord.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/apps/venus/src/assets/page.png b/apps/venus/src/assets/page.png deleted file mode 100644 index 2236ce046d..0000000000 Binary files a/apps/venus/src/assets/page.png and /dev/null differ diff --git a/apps/venus/src/assets/shape.png b/apps/venus/src/assets/shape.png deleted file mode 100644 index 178d7ea6a1..0000000000 Binary files a/apps/venus/src/assets/shape.png and /dev/null differ diff --git a/apps/venus/src/assets/task.png b/apps/venus/src/assets/task.png deleted file mode 100644 index 8ef6c95057..0000000000 Binary files a/apps/venus/src/assets/task.png and /dev/null differ diff --git a/apps/venus/src/favicon.ico b/apps/venus/src/favicon.ico deleted file mode 100644 index 317ebcb233..0000000000 Binary files a/apps/venus/src/favicon.ico and /dev/null differ diff --git a/apps/venus/webpack.config.js b/apps/venus/webpack.config.js index 8c980aef98..9b5aba0eef 100644 --- a/apps/venus/webpack.config.js +++ b/apps/venus/webpack.config.js @@ -7,6 +7,7 @@ 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 ImageMinimizerPlugin = require('image-minimizer-webpack-plugin'); const TerserPlugin = require('terser-webpack-plugin'); const Style9Plugin = require('style9/webpack'); @@ -61,6 +62,14 @@ module.exports = function (webpackConfig) { parallel: true, }), new CssMinimizerPlugin(), + new ImageMinimizerPlugin({ + minimizer: { + implementation: ImageMinimizerPlugin.imageminMinify, + options: { + plugins: [['optipng', { optimizationLevel: 5 }]], + }, + }, + }), ], splitChunks: { chunks: 'all', diff --git a/libs/components/affine-editor/src/Editor.tsx b/libs/components/affine-editor/src/Editor.tsx index 62f266b46c..4f842de2fd 100644 --- a/libs/components/affine-editor/src/Editor.tsx +++ b/libs/components/affine-editor/src/Editor.tsx @@ -18,6 +18,12 @@ interface AffineEditorProps { scrollBlank?: boolean; isWhiteboard?: boolean; + + scrollContainer?: HTMLElement; + scrollController?: { + lockScroll: () => void; + unLockScroll: () => void; + }; } function _useConstantWithDispose( @@ -53,13 +59,32 @@ function _useConstantWithDispose( } export const AffineEditor = forwardRef( - ({ workspace, rootBlockId, scrollBlank = true, isWhiteboard }, ref) => { + ( + { + workspace, + rootBlockId, + scrollBlank = true, + isWhiteboard, + scrollController, + scrollContainer, + }, + ref + ) => { const editor = _useConstantWithDispose( workspace, rootBlockId, isWhiteboard ); + useEffect(() => { + if (scrollContainer) { + editor.scrollManager.scrollContainer = scrollContainer; + } + if (scrollController) { + editor.scrollManager.scrollController = scrollController; + } + }, [editor, scrollContainer, scrollController]); + useImperativeHandle(ref, () => editor); return ( diff --git a/libs/components/board-draw/src/TlDraw.tsx b/libs/components/board-draw/src/TlDraw.tsx index 5ade114f4f..21044ad51d 100644 --- a/libs/components/board-draw/src/TlDraw.tsx +++ b/libs/components/board-draw/src/TlDraw.tsx @@ -29,6 +29,7 @@ import { ErrorBoundary } from 'react-error-boundary'; import { ErrorFallback } from './components/error-fallback'; import { ZoomBar } from './components/zoom-bar'; import { CommandPanel } from './components/command-panel'; +import { usePageClientWidth } from '@toeverything/datasource/state'; export interface TldrawProps extends TldrawAppCtorProps { /** @@ -132,6 +133,9 @@ export function Tldraw({ tools, }: TldrawProps) { const [sId, set_sid] = React.useState(id); + const { pageClientWidth } = usePageClientWidth(); + // page padding left and right total 300px + const editorShapeInitSize = pageClientWidth - 300; // Create a new app when the component mounts. const [app, setApp] = React.useState(() => { @@ -140,6 +144,7 @@ export function Tldraw({ callbacks, commands, getSession, + editorShapeInitSize, tools, }); return app; diff --git a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx b/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx index f1f6961e5a..1e73889fb2 100644 --- a/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx +++ b/libs/components/board-draw/src/components/command-panel/CommandPanel.tsx @@ -11,6 +11,7 @@ import { StrokeLineStyleConfig } from './stroke-line-style-config'; import { Group, UnGroup } from './GroupOperation'; import { DeleteShapes } from './DeleteOperation'; import { Lock, Unlock } from './LockOperation'; +import { FrameFillColorConfig } from './FrameFillColorConfig'; export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => { const state = app.useStore(); @@ -51,6 +52,13 @@ export const CommandPanel: FC<{ app: TldrawApp }> = ({ app }) => { shapes={config.fill.selectedShapes} /> ) : null, + frameFill: config.frameFill.selectedShapes.length ? ( + + ) : null, font: config.font.selectedShapes.length ? ( { + const counted = countBy(shapes, shape => shape.style.fill); + const max = maxBy(Object.entries(counted), ([c, n]) => n); + return max[0]; +}; + +export const FrameFillColorConfig: FC = ({ + app, + shapes, +}) => { + const theme = useTheme(); + const setFillColor = (color: ColorType) => { + app.style( + { fill: color, isFilled: color !== 'none' }, + getShapeIds(shapes) + ); + }; + + const iconColor = _getIconRenderColor(shapes); + + return ( + + } + > + + + {iconColor === 'none' ? ( + + ) : ( + + )} + + + + ); +}; diff --git a/libs/components/board-draw/src/components/command-panel/utils/use-config.ts b/libs/components/board-draw/src/components/command-panel/utils/use-config.ts index 05b08b46c6..6071eb8dcc 100644 --- a/libs/components/board-draw/src/components/command-panel/utils/use-config.ts +++ b/libs/components/board-draw/src/components/command-panel/utils/use-config.ts @@ -7,6 +7,7 @@ interface Config { type: | 'stroke' | 'fill' + | 'frameFill' | 'font' | 'group' | 'ungroup' @@ -22,6 +23,10 @@ const _createInitConfig = (): Record => { type: 'fill', selectedShapes: [], }, + frameFill: { + type: 'frameFill', + selectedShapes: [], + }, stroke: { type: 'stroke', selectedShapes: [], @@ -64,6 +69,7 @@ const _isSupportStroke = (shape: TDShape): boolean => { TDShapeType.Pencil, TDShapeType.Laser, TDShapeType.Highlight, + TDShapeType.Draw, TDShapeType.Arrow, TDShapeType.Line, ].some(type => type === shape.type); @@ -91,6 +97,10 @@ const _isSupportFont = (shape: TDShape): boolean => { ].some(type => type === shape.type); }; +const _isSupportFrameFill = (shape: TDShape): boolean => { + return shape.type === TDShapeType.Frame; +}; + export const useConfig = (app: TldrawApp): Record => { const state = app.useStore(); const selectedShapes = TLDR.get_selected_shapes(state, app.currentPageId); @@ -105,6 +115,9 @@ export const useConfig = (app: TldrawApp): Record => { if (_isSupportFont(cur)) { acc.font.selectedShapes.push(cur); } + if (_isSupportFrameFill(cur)) { + acc.frameFill.selectedShapes.push(cur); + } return acc; }, _createInitConfig() diff --git a/libs/components/board-sessions/src/rotate-session.ts b/libs/components/board-sessions/src/rotate-session.ts index e9da6b6bcc..2de48ed120 100644 --- a/libs/components/board-sessions/src/rotate-session.ts +++ b/libs/components/board-sessions/src/rotate-session.ts @@ -6,6 +6,7 @@ import { TldrawPatch, TDShape, TDStatus, + TDShapeType, } from '@toeverything/components/board-types'; import { TLDR } from '@toeverything/components/board-state'; import { BaseSession } from './base-session'; @@ -75,6 +76,10 @@ export class RotateSession extends BaseSession { app: { currentPageId, currentPoint, shiftKey }, } = this; + const filteredShapes = initialShapes.filter( + shape => shape.shape.type !== TDShapeType.Editor + ); + const shapes: Record> = {}; let directionDelta = @@ -85,7 +90,7 @@ export class RotateSession extends BaseSession { } // Update the shapes - initialShapes.forEach(({ center, shape }) => { + filteredShapes.forEach(({ center, shape }) => { const { rotation = 0 } = shape; let shapeDelta = 0; diff --git a/libs/components/board-shapes/src/frame-util/frame-util.tsx b/libs/components/board-shapes/src/frame-util/FrameUtil.tsx similarity index 67% rename from libs/components/board-shapes/src/frame-util/frame-util.tsx rename to libs/components/board-shapes/src/frame-util/FrameUtil.tsx index f8db4d09ff..47fc778da7 100644 --- a/libs/components/board-shapes/src/frame-util/frame-util.tsx +++ b/libs/components/board-shapes/src/frame-util/FrameUtil.tsx @@ -1,12 +1,9 @@ -import * as React from 'react'; +/* eslint-disable no-restricted-syntax */ import { Utils, SVGContainer } from '@tldraw/core'; import { FrameShape, - DashStyle, TDShapeType, TDMeta, - GHOSTED_OPACITY, - LABEL_POINT, } from '@toeverything/components/board-types'; import { TDShapeUtil } from '../TDShapeUtil'; import { @@ -14,14 +11,13 @@ import { getShapeStyle, getBoundsRectangle, transformRectangle, - getFontStyle, transformSingleRectangle, } from '../shared'; -import { DrawFrame } from './components/draw-frame'; +import { Frame } from './components/Frame'; import { styled } from '@toeverything/components/ui'; type T = FrameShape; -type E = HTMLDivElement; +type E = SVGSVGElement; export class FrameUtil extends TDShapeUtil { type = TDShapeType.Frame as const; @@ -56,10 +52,7 @@ export class FrameUtil extends TDShapeUtil { ( { shape, - isEditing, - isBinding, isSelected, - isGhost, meta, bounds, events, @@ -70,21 +63,20 @@ export class FrameUtil extends TDShapeUtil { ) => { const { id, size, style } = shape; return ( - - - - - + + + ); } ); @@ -121,27 +113,9 @@ export class FrameUtil extends TDShapeUtil { override transform = transformRectangle; override transformSingle = transformSingleRectangle; - - override hitTestPoint = (shape: T, point: number[]): boolean => { - return false; - }; - - override hitTestLineSegment = ( - shape: T, - A: number[], - B: number[] - ): boolean => { - return false; - }; } const FullWrapper = styled('div')({ width: '100%', height: '100%', - '.tl-fill-hitarea': { - fill: '#F7F9FF', - }, - '.tl-stroke-hitarea': { - fill: '#F7F9FF', - }, }); diff --git a/libs/components/board-shapes/src/frame-util/components/Frame.tsx b/libs/components/board-shapes/src/frame-util/components/Frame.tsx new file mode 100644 index 0000000000..57a6fa878a --- /dev/null +++ b/libs/components/board-shapes/src/frame-util/components/Frame.tsx @@ -0,0 +1,54 @@ +import * as React from 'react'; +import { BINDING_DISTANCE } from '@toeverything/components/board-types'; +import type { ShapeStyles } from '@toeverything/components/board-types'; +import { getShapeStyle } from '../../shared'; + +interface RectangleSvgProps { + id: string; + style: ShapeStyles; + isSelected: boolean; + size: number[]; + isDarkMode: boolean; +} + +export const Frame = React.memo(function DashedRectangle({ + id, + style, + size, + isSelected, + isDarkMode, +}: RectangleSvgProps) { + const { strokeWidth, fill } = getShapeStyle(style, isDarkMode); + + const _fill = fill && fill !== 'none' ? fill : '#F7F9FF'; + + const sw = 1 + strokeWidth * 1.618; + + const w = Math.max(0, size[0] - sw / 2); + const h = Math.max(0, size[1] - sw / 2); + + return ( + <> + + + + ); +}); diff --git a/libs/components/board-shapes/src/frame-util/components/draw-frame.tsx b/libs/components/board-shapes/src/frame-util/components/draw-frame.tsx deleted file mode 100644 index 4074f76b15..0000000000 --- a/libs/components/board-shapes/src/frame-util/components/draw-frame.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import * as React from 'react'; -import { BINDING_DISTANCE } from '@toeverything/components/board-types'; -import type { ShapeStyles } from '@toeverything/components/board-types'; -import { getShapeStyle } from '../../shared'; - -interface RectangleSvgProps { - id: string; - style: ShapeStyles; - isSelected: boolean; - size: number[]; - isDarkMode: boolean; -} - -export const DrawFrame = React.memo(function DashedRectangle({ - id, - style, - size, - isSelected, - isDarkMode, -}: RectangleSvgProps) { - const { stroke, strokeWidth, fill } = getShapeStyle(style, isDarkMode); - - const sw = 1 + strokeWidth * 1.618; - - const w = Math.max(0, size[0] - sw / 2); - const h = Math.max(0, size[1] - sw / 2); - - return ( - - ); -}); diff --git a/libs/components/board-shapes/src/frame-util/index.ts b/libs/components/board-shapes/src/frame-util/index.ts index bda0014cda..5401a5fd24 100644 --- a/libs/components/board-shapes/src/frame-util/index.ts +++ b/libs/components/board-shapes/src/frame-util/index.ts @@ -1 +1 @@ -export * from './frame-util'; +export * from './FrameUtil'; diff --git a/libs/components/board-state/src/tldraw-app.ts b/libs/components/board-state/src/tldraw-app.ts index c431a90bde..ab9492aabb 100644 --- a/libs/components/board-state/src/tldraw-app.ts +++ b/libs/components/board-state/src/tldraw-app.ts @@ -73,6 +73,7 @@ import { StateManager } from './manager/state-manager'; import { getClipboard, setClipboard } from './idb-clipboard'; import type { Commands } from './types/commands'; import type { BaseTool } from './types/tool'; +import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core'; const uuid = Utils.uniqueId(); @@ -178,6 +179,7 @@ export interface TldrawAppCtorProps { getSession: (type: SessionType) => { new (app: TldrawApp, ...args: any[]): BaseSessionType; }; + editorShapeInitSize?: number; commands: Commands; tools: Record; } @@ -223,6 +225,8 @@ export class TldrawApp extends StateManager { fileSystemHandle: FileSystemHandle | null = null; + editorShapeInitSize = MIN_PAGE_WIDTH; + viewport = Utils.getBoundsFromPoints([ [0, 0], [100, 100], @@ -285,6 +289,10 @@ export class TldrawApp extends StateManager { return acc; }, {} as Record); this.currentTool = this.tools['select']; + + if (props.editorShapeInitSize) { + this.editorShapeInitSize = props.editorShapeInitSize; + } } /* -------------------- Internal -------------------- */ diff --git a/libs/components/board-tools/src/editor-tool/editor-tool.ts b/libs/components/board-tools/src/editor-tool/editor-tool.ts index 4f1d20a2d6..f0da22a0c1 100644 --- a/libs/components/board-tools/src/editor-tool/editor-tool.ts +++ b/libs/components/board-tools/src/editor-tool/editor-tool.ts @@ -18,6 +18,7 @@ export class EditorTool extends BaseTool { const { currentPoint, currentGrid, + editorShapeInitSize, settings: { showGrid }, appState: { currentPageId, currentStyle }, document: { id: workspace }, @@ -47,6 +48,7 @@ export class EditorTool extends BaseTool { ? Vec.snap(currentPoint, currentGrid) : currentPoint, style: { ...currentStyle }, + size: [editorShapeInitSize, 200], workspace, }); // In order to make the cursor just positioned at the beginning of the first line, it needs to be adjusted according to the padding newShape.point = Vec.sub(newShape.point, [50, 30]); diff --git a/libs/components/common/src/lib/collapsible-title/index.tsx b/libs/components/common/src/lib/collapsible-title/index.tsx index 2612eb0aa7..7dd59e613b 100644 --- a/libs/components/common/src/lib/collapsible-title/index.tsx +++ b/libs/components/common/src/lib/collapsible-title/index.tsx @@ -1,16 +1,21 @@ import { useState } from 'react'; -import clsx from 'clsx'; -import style9 from 'style9'; import { MuiButton as Button, MuiCollapse as Collapse, + styled, } from '@toeverything/components/ui'; -import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; -import ArrowRightIcon from '@mui/icons-material/ArrowRight'; +import { + ArrowDropDownIcon, + ArrowRightIcon, +} from '@toeverything/components/icons'; -const styles = style9.create({ - ligoButton: { - textTransform: 'none', +const StyledContainer = styled('div')({ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + '&:hover': { + background: '#f5f7f8', + borderRadius: '5px', }, }); @@ -24,29 +29,32 @@ export type CollapsibleTitleProps = { }; export function CollapsibleTitle(props: CollapsibleTitleProps) { - const { className, style, children, title, initialOpen = true } = props; + const { children, title, initialOpen = true } = props; const [open, setOpen] = useState(initialOpen); return ( <> - + setOpen(prev => !prev)}> + {open ? ( + + ) : ( + + )} +
+ {title} +
+
{children ? ( {children} diff --git a/libs/components/common/src/lib/text/plugins/link.tsx b/libs/components/common/src/lib/text/plugins/link.tsx index 18679b27d7..60193e38a0 100644 --- a/libs/components/common/src/lib/text/plugins/link.tsx +++ b/libs/components/common/src/lib/text/plugins/link.tsx @@ -121,11 +121,11 @@ const isLinkActive = (editor: ReactEditor) => { const LinkStyledTooltip = styled(({ className, ...props }: MuiTooltipProps) => ( -))(() => ({ +))(({ theme }) => ({ [`& .${muiTooltipClasses.tooltip}`]: { backgroundColor: '#fff', color: '#4C6275', - boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', + boxShadow: theme.affine.shadows.shadow1, fontSize: '14px', }, [`& .MuiTooltip-tooltipPlacementBottom`]: { @@ -412,8 +412,7 @@ export const LinkModal = memo((props: LinkModalProps) => { visible && ( <> -
{ autoComplete="off" ref={inputEl} /> -
+ ), body @@ -491,19 +490,20 @@ const LinkBehavior = (props: { ); }; +const LinkModalContainer = styled('div')(({ theme }) => ({ + position: 'fixed', + width: '354px', + height: '40px', + padding: '12px', + display: 'flex', + borderRadius: '4px', + boxShadow: theme.affine.shadows.shadow1, + backgroundColor: '#fff', + alignItems: 'center', + zIndex: '1', +})); + const styles = style9.create({ - linkModalContainer: { - position: 'fixed', - width: '354px', - height: '40px', - padding: '12px', - display: 'flex', - borderRadius: '4px', - boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', - backgroundColor: '#fff', - alignItems: 'center', - zIndex: '1', - }, linkModalContainerIcon: { width: '16px', margin: '0 16px 0 4px', diff --git a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx index 6f1970df15..dee9042e7a 100644 --- a/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx +++ b/libs/components/editor-blocks/src/blocks/bullet/BulletView.tsx @@ -17,7 +17,7 @@ import { supportChildren, RenderBlockChildren, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { List } from '../../components/style-container'; import { getChildrenType, BulletIcon, NumberType } from './data'; @@ -188,7 +188,7 @@ export const BulletView: FC = ({ block, editor }) => { return ( - + @@ -206,7 +206,7 @@ export const BulletView: FC = ({ block, editor }) => { />
- + diff --git a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx index 3a729e51f0..3b64cc3b1a 100644 --- a/libs/components/editor-blocks/src/blocks/code/CodeView.tsx +++ b/libs/components/editor-blocks/src/blocks/code/CodeView.tsx @@ -50,7 +50,7 @@ import { Option, Select } from '@toeverything/components/ui'; import { useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { copyToClipboard } from '@toeverything/utils'; interface CreateCodeView extends CreateView { @@ -163,7 +163,7 @@ export const CodeView: FC = ({ block, editor }) => { editor.selectionManager.activePreviousNode(block.id, 'start'); }; return ( - + { e.stopPropagation(); @@ -222,6 +222,6 @@ export const CodeView: FC = ({ block, editor }) => { handleKeyArrowUp={handleKeyArrowUp} /> - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx b/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx index ac073abdb0..da33c2be4a 100644 --- a/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx +++ b/libs/components/editor-blocks/src/blocks/embed-link/EmbedLinkView.tsx @@ -1,7 +1,7 @@ import { FC, useState } from 'react'; import { CreateView } from '@toeverything/framework/virgo'; import { - WrapperWithPendantAndDragDrop, + BlockPendantProvider, useOnSelect, } from '@toeverything/components/editor-core'; import { Upload } from '../../components/upload/upload'; @@ -33,7 +33,7 @@ export const EmbedLinkView: FC = props => { }; return ( - + {embedLinkUrl ? ( = props => { /> )} - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx b/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx index 1dbaef3e11..2d12315235 100644 --- a/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx +++ b/libs/components/editor-blocks/src/blocks/figma/FigmaView.tsx @@ -2,7 +2,7 @@ import { FC, useState } from 'react'; import { CreateView } from '@toeverything/framework/virgo'; import { useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { Upload } from '../../components/upload/upload'; import { SourceView } from '../../components/source-view'; @@ -30,7 +30,7 @@ export const FigmaView: FC = ({ block, editor }) => { setIsSelect(isSelect); }); return ( - + {figmaUrl ? ( = ({ block, editor }) => { /> )} - + ); }; diff --git a/libs/components/editor-blocks/src/blocks/grid/GirdHandle.tsx b/libs/components/editor-blocks/src/blocks/grid/GirdHandle.tsx index e6464860c5..687cc28735 100644 --- a/libs/components/editor-blocks/src/blocks/grid/GirdHandle.tsx +++ b/libs/components/editor-blocks/src/blocks/grid/GirdHandle.tsx @@ -12,6 +12,8 @@ type GridHandleProps = { blockId: string; enabledAddItem: boolean; draggable: boolean; + alertHandleId: string; + onMouseEnter?: React.MouseEventHandler; }; export const GridHandle: FC = function ({ @@ -21,6 +23,8 @@ export const GridHandle: FC = function ({ onDrag, onMouseDown, draggable, + alertHandleId, + onMouseEnter, }) { const [isMouseDown, setIsMouseDown] = useState(false); const handleMouseDown: React.MouseEventHandler = e => { @@ -44,16 +48,17 @@ export const GridHandle: FC = function ({ editor.selectionManager.setActivatedNodeId(textBlock.id); } }; + + const handleMouseEnter: React.MouseEventHandler = e => { + onMouseEnter && onMouseEnter(e); + }; + return ( {enabledAddItem ? ( = function ({ ); }; -const GridHandleContainer = styled('div')(({ theme }) => ({ +const GridHandleContainer = styled('div')<{ + isMouseDown: boolean; + isAlert: boolean; +}>(({ theme, isMouseDown, isAlert }) => ({ position: 'relative', width: '10px', flexGrow: '0', @@ -78,11 +86,15 @@ const GridHandleContainer = styled('div')(({ theme }) => ({ borderRadius: '1px', backgroundClip: 'content-box', ' &:hover': { - backgroundColor: theme.affine.palette.primary, + backgroundColor: isAlert ? 'red' : theme.affine.palette.primary, [`.${GRID_ADD_HANDLE_NAME}`]: { display: 'block', }, }, + ...(isMouseDown && + (isAlert + ? { backgroundColor: 'red' } + : { backgroundColor: theme.affine.palette.primary })), })); const AddGridHandle = styled('div')(({ theme }) => ({ diff --git a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx index 77a83ff599..bc27ec4ac6 100644 --- a/libs/components/editor-blocks/src/blocks/grid/Grid.tsx +++ b/libs/components/editor-blocks/src/blocks/grid/Grid.tsx @@ -31,6 +31,7 @@ export const Grid: FC = function (props) { const gridItemCountRef = useRef(); const originalLeftWidth = useRef(GRID_ITEM_MIN_WIDTH); const originalRightWidth = useRef(GRID_ITEM_MIN_WIDTH); + const [alertHandleId, setAlertHandleId] = useState(null); const getLeftRightGridItemDomByIndex = (index: number) => { const gridItems = Array.from(gridContainerRef.current?.children).filter( @@ -117,7 +118,7 @@ export const Grid: FC = function (props) { itemDom.style.width = width; }; - const handleDragGrid = (e: MouseEvent, index: number) => { + const handleDragGrid = async (e: MouseEvent, index: number) => { setIsOnDrag(true); window.getSelection().removeAllRanges(); if (!isSetMouseUp.current) { @@ -165,39 +166,47 @@ export const Grid: FC = function (props) { setItemWidth(leftGrid, newLeft); setItemWidth(rightGrid, newRight); updateDbWidth(leftBlockId, newLeft, rightBlockId, newRight); + [leftBlockId, rightBlockId].forEach(async blockId => { + if (await checkGridItemHasOverflow(blockId)) { + setAlertHandleId(leftBlockId); + } else { + setAlertHandleId(null); + } + }); } } }; - const children = ( - <> - {block.childrenIds.map((id, i) => { - return ( - - - handleDragGrid(event, i)} - editor={editor} - onMouseDown={event => handleMouseDown(event, i)} - blockId={id} - enabledAddItem={ - block.childrenIds.length < MAX_ITEM_COUNT - } - draggable={i !== block.childrenIds.length - 1} - /> - - ); - })} - - ); + const checkGridItemHasOverflow = async (blockId: string) => { + let isOverflow = false; + const block = await editor.getBlockById(blockId); + if (block) { + const blockDom = block.dom; + if (blockDom) { + block.dom.style.overflow = 'scroll'; + if (block.dom.clientWidth !== block.dom.scrollWidth) { + isOverflow = true; + } + blockDom.style.overflow = 'visible'; + } + } + return isOverflow; + }; + + const handleHandleMouseEnter = ( + e: React.MouseEvent, + index: number + ) => { + const leftBlockId = block.childrenIds[index]; + const rightBlockId = block.childrenIds[index + 1]; + [leftBlockId, rightBlockId].forEach(async blockId => { + if (await checkGridItemHasOverflow(blockId)) { + setAlertHandleId(leftBlockId); + } else { + setAlertHandleId(null); + } + }); + }; return ( <> @@ -206,7 +215,35 @@ export const Grid: FC = function (props) { ref={gridContainerRef} isOnDrag={isOnDrag} > - {children} + {block.childrenIds.map((id, i) => { + return ( + + + handleDragGrid(event, i)} + editor={editor} + onMouseDown={event => handleMouseDown(event, i)} + blockId={id} + enabledAddItem={ + block.childrenIds.length < MAX_ITEM_COUNT + } + onMouseEnter={event => + handleHandleMouseEnter(event, i) + } + alertHandleId={alertHandleId} + draggable={i !== block.childrenIds.length - 1} + /> + + ); + })} {isOnDrag ? ReactDOM.createPortal(, window.document.body) diff --git a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx index 694fe7820f..193a97f076 100644 --- a/libs/components/editor-blocks/src/blocks/group/GroupView.tsx +++ b/libs/components/editor-blocks/src/blocks/group/GroupView.tsx @@ -60,7 +60,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>( ({ isSelect, theme }) => ({ background: theme.affine.palette.white, border: '2px solid rgba(236,241,251,.5)', - padding: '15px 12px', + padding: `15px 16px 0 16px`, borderRadius: '10px', ...(isSelect ? { @@ -69,7 +69,7 @@ const GroupContainer = styled('div')<{ isSelect?: boolean }>( } : { '&:hover': { - boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)', + boxShadow: theme.affine.shadows.shadow1, }, }), }) diff --git a/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx b/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx index e8faba84a1..d6d4b79f02 100644 --- a/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx +++ b/libs/components/editor-blocks/src/blocks/group/components/Panel.tsx @@ -2,11 +2,11 @@ import { styled } from '@toeverything/components/ui'; import type { ComponentPropsWithRef, MouseEvent } from 'react'; import { forwardRef } from 'react'; -const StyledPanel = styled('div')(() => ({ +const StyledPanel = styled('div')(({ theme }) => ({ position: 'absolute', top: 50, background: '#FFFFFF', - boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)', + boxShadow: theme.affine.shadows.shadow1, borderRadius: 10, padding: '12px 24px', })); diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx index 3616c0d206..cd71b72666 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContainer.tsx @@ -41,6 +41,7 @@ const getKanbanColor = ( return DEFAULT_COLOR; } if ( + group.type === PropertyType.Status || group.type === PropertyType.Select || group.type === PropertyType.MultiSelect || group.type === DEFAULT_GROUP_ID diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx index cb313d14ee..f7f05b37d4 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardContext.tsx @@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => { const { addCard } = useKanban(); const handleClick = useCallback(async () => { await addCard(group); - }, [addCard]); + }, [addCard, group]); return +; }; diff --git a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx index f26374e4c3..76220d7ff0 100644 --- a/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx +++ b/libs/components/editor-blocks/src/blocks/group/scene-kanban/CardItem.tsx @@ -1,6 +1,11 @@ import type { KanbanCard } from '@toeverything/components/editor-core'; -import { RenderBlock, useKanban } from '@toeverything/components/editor-core'; +import { + RenderBlock, + useKanban, + useRefPage, +} from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; +import { useFlag } from '@toeverything/datasource/feature-flags'; const CardContent = styled('div')({ margin: '20px', @@ -58,18 +63,24 @@ export const CardItem = ({ block: KanbanCard['block']; }) => { const { addSubItem } = useKanban(); + const { openSubPage } = useRefPage(); + const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false); const onAddItem = async () => { await addSubItem(block); }; + const onClickCard = async () => { + showKanbanRefPageFlag && openSubPage(id); + }; + return ( - + - Add item + Add a sub-block ); diff --git a/libs/components/editor-blocks/src/blocks/image/ImageView.tsx b/libs/components/editor-blocks/src/blocks/image/ImageView.tsx index 36b446d790..369dceac87 100644 --- a/libs/components/editor-blocks/src/blocks/image/ImageView.tsx +++ b/libs/components/editor-blocks/src/blocks/image/ImageView.tsx @@ -1,7 +1,7 @@ import { useCurrentView, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; import { services } from '@toeverything/datasource/db-service'; @@ -143,13 +143,13 @@ export const ImageView: FC = ({ block, editor }) => { type: 'link', }); }; - const handle_click = (e: React.MouseEvent) => { + const handle_click = async (e: React.MouseEvent) => { //TODO clear active selection // document.getElementsByTagName('body')[0].click(); e.stopPropagation(); e.nativeEvent.stopPropagation(); - editor.selectionManager.setSelectedNodesIds([block.id]); - editor.selectionManager.activeNodeByNodeId(block.id); + await editor.selectionManager.setSelectedNodesIds([block.id]); + await editor.selectionManager.activeNodeByNodeId(block.id, 'end'); }; const down_file = () => { if (down_ref) { @@ -158,7 +158,7 @@ export const ImageView: FC = ({ block, editor }) => { }; return ( - +
{imgUrl ? ( @@ -229,6 +229,6 @@ export const ImageView: FC = ({ block, editor }) => {
*/}
-
+ ); }; diff --git a/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx b/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx index 514ce335f6..2f8f29b706 100644 --- a/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx +++ b/libs/components/editor-blocks/src/blocks/numbered/NumberedView.tsx @@ -19,9 +19,8 @@ import { supportChildren, RenderBlockChildren, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; -import { styled } from '@toeverything/components/ui'; import { List } from '../../components/style-container'; import { BlockContainer } from '../../components/BlockContainer'; @@ -185,7 +184,7 @@ export const NumberedView: FC = ({ block, editor }) => { return ( - +
{getNumber(properties.numberType, number)}. @@ -203,7 +202,7 @@ export const NumberedView: FC = ({ block, editor }) => { />
-
+ diff --git a/libs/components/editor-blocks/src/blocks/text/TextView.tsx b/libs/components/editor-blocks/src/blocks/text/TextView.tsx index ced1a8745d..cb408cb189 100644 --- a/libs/components/editor-blocks/src/blocks/text/TextView.tsx +++ b/libs/components/editor-blocks/src/blocks/text/TextView.tsx @@ -8,7 +8,7 @@ import { supportChildren, unwrapGroup, useOnSelect, - WrapperWithPendantAndDragDrop, + BlockPendantProvider, } from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; import { Protocol } from '@toeverything/datasource/db-service'; @@ -99,7 +99,7 @@ export const TextView: FC = ({ if (!parentBlock) { return false; } - + const preParent = await parentBlock.previousSibling(); if (Protocol.Block.Type.group === parentBlock.type) { const children = await block.children(); const preNode = await block.physicallyPerviousSibling(); @@ -129,34 +129,19 @@ export const TextView: FC = ({ 'start' ); if (block.blockProvider.isEmpty()) { - block.remove(); - } - } - return true; - } else { - // TODO remove timing problem - const prevGroupBlock = await parentBlock.previousSibling(); - - if (!prevGroupBlock) { - const childrenBlock = await parentBlock.children(); - if (childrenBlock.length) { - if (children.length) { - await parentBlock.append(...children); - } await block.remove(); - return true; + const parentChild = await parentBlock.children(); + if ( + parentBlock.type === + Protocol.Block.Type.group && + !parentChild.length + ) { + await editor.selectionManager.setSelectedNodesIds( + [preParent?.id ?? editor.getRootBlockId()] + ); + } } - - parentBlock.remove(); - return true; } - if (prevGroupBlock.type !== Protocol.Block.Type.group) { - unwrapGroup(parentBlock); - return true; - } - - mergeGroup(prevGroupBlock, parentBlock); - return true; } } @@ -231,7 +216,7 @@ export const TextView: FC = ({ selected={isSelect} className={containerClassName} > - + = ({ handleConvert={handleConvert} handleTab={onTab} /> - + diff --git a/libs/components/editor-blocks/src/components/source-view/SourceView.tsx b/libs/components/editor-blocks/src/components/source-view/SourceView.tsx index 3835199d3f..4dbb488850 100644 --- a/libs/components/editor-blocks/src/components/source-view/SourceView.tsx +++ b/libs/components/editor-blocks/src/components/source-view/SourceView.tsx @@ -1,6 +1,18 @@ -import type { AsyncBlock } from '@toeverything/components/editor-core'; +import { + AsyncBlock, + useCurrentView, + useLazyIframe, +} from '@toeverything/components/editor-core'; import { styled } from '@toeverything/components/ui'; -import type { FC } from 'react'; +import { + FC, + ReactElement, + ReactNode, + useEffect, + useRef, + useState, +} from 'react'; +import { SCENE_CONFIG } from '../../blocks/group/config'; import { BlockPreview } from './BlockView'; import { formatUrl } from './format-url'; @@ -15,7 +27,18 @@ export interface Props { } const getHost = (url: string) => new URL(url).host; - +const MouseMaskContainer = styled('div')({ + position: 'absolute', + zIndex: 1, + top: '0px', + left: '0px', + right: '0px', + bottom: '0px', + backgroundColor: 'transparent', + '&:hover': { + pointerEvents: 'none', + }, +}); const LinkContainer = styled('div')<{ isSelected: boolean; }>(({ theme, isSelected }) => { @@ -38,12 +61,28 @@ const LinkContainer = styled('div')<{ }, }; }); - +const _getLinkStyle = (scene: string) => { + switch (scene) { + case SCENE_CONFIG.PAGE: + return { + width: '420px', + height: '198px', + }; + default: + return { + width: '252px', + height: '126px', + }; + } +}; const SourceViewContainer = styled('div')<{ isSelected: boolean; -}>(({ theme, isSelected }) => { + scene: string; +}>(({ theme, isSelected, scene }) => { return { + ..._getLinkStyle(scene), overflow: 'hidden', + position: 'relative', borderRadius: theme.affine.shape.borderRadius, background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent', padding: '8px', @@ -52,32 +91,96 @@ const SourceViewContainer = styled('div')<{ height: '100%', border: '1px solid #EAEEF2', borderRadius: theme.affine.shape.borderRadius, + userSelect: 'none', }, }; }); +const LazyIframe = ({ + src, + delay = 3000, + fallback, +}: { + src: string; + delay?: number; + fallback?: ReactNode; +}) => { + const [show, setShow] = useState(false); + const timer = useRef(); + + useEffect(() => { + // Hide iframe when the src changed + setShow(false); + }, [src]); + + const onLoad = () => { + clearTimeout(timer.current); + timer.current = window.setTimeout(() => { + // Prevent iframe scrolling parent container + // Remove the delay after the issue is resolved + // See W3C https://github.com/w3c/csswg-drafts/issues/7134 + // See https://forum.figma.com/t/prevent-figmas-embed-code-from-automatically-scrolling-to-it-on-page-load/26029/6 + setShow(true); + }, delay); + }; + + return ( + <> +
{ + e.preventDefault(); + e.stopPropagation(); + }} + style={{ display: show ? 'block' : 'none', height: '100%' }} + > +

DarkSky

💻 📖

Chi Zhang

💻 📖

alt1o

💻 📖

Diamond

💻 📖

Chi Zhang

💻 📖

wang xinglong

💻 📖

DiamondThree

💻 📖

Whitewater

💻 📖

zuoxiaodong0815

💻 📖

SaikaSakura

💻 📖

xiaodong zuo

💻 📖

MingLIang Wang

💻 📖

Qi

💻 📖

tuluffy

💻 📖

mitsuhatu

💻 📖

Austaras

💻 📖

Jin Yao

💻 📖