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
-
+
-# Stay Up-to-Date
+# Stay Up-to-Date and Support Us

+# 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
DarkSky 💻 📖
- Chi Zhang 💻 📖
- alt1o 💻 📖
- Diamond 💻 📖
+ Chi Zhang 💻 📖
+ wang xinglong 💻 📖
+ DiamondThree 💻 📖
Whitewater 💻 📖
- zuoxiaodong0815 💻 📖
- SaikaSakura 💻 📖
+ xiaodong zuo 💻 📖
+ MingLIang Wang 💻 📖
Qi 💻 📖
- tuluffy 💻 📖
+ mitsuhatu 💻 📖
Austaras 💻 📖
Jin Yao 💻 📖
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) {
)}
@@ -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),
+ }}
/>
) : (
({
+const WorkspaceSidebar = styled('div')(({ theme }) => ({
position: 'absolute',
bottom: '48px',
top: '12px',
@@ -208,7 +177,7 @@ const WorkspaceSidebar = styled('div')(({ hidden }) => ({
width: 300,
minWidth: 300,
borderRadius: '0px 10px 10px 0px',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ boxShadow: theme.affine.shadows.shadow1,
backgroundColor: '#FFFFFF',
transitionProperty: 'left',
transitionDuration: '0.35s',
@@ -219,4 +188,5 @@ const WorkspaceSidebar = styled('div')(({ hidden }) => ({
const WorkspaceSidebarContent = styled('div')({
flex: 'auto',
overflow: 'hidden auto',
+ marginTop: '18px',
});
diff --git a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx
index 069bc48857..ab903ab3ca 100644
--- a/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx
+++ b/apps/ligo-virgo/src/pages/workspace/docs/collapsible-page-tree.tsx
@@ -1,34 +1,34 @@
-import { useCallback, useState } from 'react';
+import React, { useCallback, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
-import clsx from 'clsx';
-import style9 from 'style9';
import {
MuiBox as Box,
MuiButton as Button,
MuiCollapse as Collapse,
MuiIconButton as IconButton,
+ 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';
import { services } from '@toeverything/datasource/db-service';
-import { NewpageIcon } from '@toeverything/components/common';
import {
usePageTree,
useCalendarHeatmap,
} from '@toeverything/components/layout';
+import { AddIcon } from '@toeverything/components/icons';
-const styles = style9.create({
- ligoButton: {
- textTransform: 'none',
- },
- newPage: {
- color: '#B6C7D3',
- width: '26px',
- fontSize: '18px',
- textAlign: 'center',
- cursor: 'pointer',
- },
+const StyledContainer = styled('div')({
+ height: '32px',
+ display: 'flex',
+ alignItems: 'center',
+});
+
+const StyledBtn = styled('div')({
+ color: '#98ACBD',
+ textTransform: 'none',
+ fontSize: '12px',
+ fontWeight: '600',
});
export type CollapsiblePageTreeProps = {
@@ -73,31 +73,35 @@ export function CollapsiblePageTree(props: CollapsiblePageTreeProps) {
justifyContent: 'space-between',
alignItems: 'center',
paddingRight: 1,
+ '&:hover': {
+ background: '#f5f7f8',
+ borderRadius: '5px',
+ },
}}
onMouseEnter={() => setNewPageBtnVisible(true)}
onMouseLeave={() => setNewPageBtnVisible(false)}
>
- :
- }
- onClick={() => setOpen(prev => !prev)}
- sx={{ color: '#566B7D', textTransform: 'none' }}
- className={clsx(styles('ligoButton'), className)}
- style={style}
- disableElevation
- disableRipple
- >
- {title}
-
+
+ {open ? (
+
+ ) : (
+
+ )}
+ setOpen(prev => !prev)}>
+ {title}
+
+
{newPageBtnVisible && (
-
- +
-
+ />
)}
{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 (
<>
-
- ) : (
-
- )
- }
- onClick={() => setOpen(prev => !prev)}
- sx={{ color: '#566B7D', textTransform: 'none' }}
- className={clsx(styles('ligoButton'), className)}
- style={style}
- disableElevation
- disableRipple
- >
- {title}
-
+ 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%' }}
+ >
+
+
+ {!show && fallback}
+ >
+ );
+};
+
+const Loading = styled('div')(() => {
+ return {
+ width: '100%',
+ height: '100%',
+ display: 'flex',
+ lineHeight: '100%',
+ alignItems: 'center',
+ justifyContent: 'center',
+ border: '1px solid #EAEEF2',
+ };
+});
+
+const LoadingContiner = () => {
+ return loading... ;
+};
+
export const SourceView: FC = props => {
const { link, isSelected, block, editorElement } = props;
const src = formatUrl(link);
- const openTabOnBrowser = () => {
- window.open(link, '_blank');
- };
+ // let iframeShow = useLazyIframe(src, 3000, iframeContainer);
+ const [currentView] = useCurrentView();
+ const { type } = currentView;
if (src?.startsWith('http')) {
return (
- e.preventDefault()}
- onClick={openTabOnBrowser}
- >
- {getHost(src)}
- {src}
-
+
+
+
+
+
+
+
);
} else if (src?.startsWith('affine')) {
return (
>(({ lastItem, children, onClick, ...restProps }, ref) => {
return (
-
-
+
+
+
+
+ {lastItem && }
+
{/* maybe need a child wrapper */}
{children}
-
+
);
});
@@ -71,10 +56,7 @@ const ChildrenView = ({
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
return (
-
+
{childrenIds.map((childId, idx) => {
if (isKanbanScene) {
return (
@@ -94,7 +76,7 @@ const ChildrenView = ({
);
})}
-
+
);
};
@@ -104,9 +86,7 @@ const CollapsedNode = forwardRef<
>((props, ref) => {
return (
-
- ···
-
+ ···
);
});
@@ -146,11 +126,11 @@ export const withTreeViewChildren = (
editor={props.editor}
block={block}
selected={isSelect}
- className={styles('wrapper')}
+ className={Wrapper.toString()}
>
-
- {creator(props)}
-
+
+ {creator(props)}
+
{collapsed && (
(({ last }) => ({
+ width: '1px',
+ height: last ? ITEM_POINT_HEIGHT : '100%',
+ paddingTop: 0,
+ paddingBottom: 0,
+ transform: 'translate(-50%, 0)',
- lastItemHorizontalLine: {
- opacity: 0,
- },
- lastItemVerticalLine: {
- height: itemPointHeight,
- opacity: 0,
- },
- lastItemRadius: {
- boxSizing: 'content-box',
- position: 'absolute',
- left: '-0.5px',
- top: 0,
- height: itemPointHeight,
- bottom: '50%',
- width: '16px',
- borderWidth: '1px',
- borderStyle: 'solid',
- borderLeftColor: treeColor,
- borderBottomColor: treeColor,
- borderTop: 'none',
- borderRight: 'none',
- borderRadius: '0 0 0 3px',
- pointerEvents: 'none',
- },
+ opacity: last ? 0 : 'unset',
+}));
- collapsed: {
- cursor: 'pointer',
- display: 'inline-block',
- color: '#B9CAD5',
- },
+const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
+ width: '16px',
+ height: '1px',
+ paddingLeft: 0,
+ paddingRight: 0,
+ top: ITEM_POINT_HEIGHT,
+ transform: 'translate(0, -50%)',
+ opacity: last ? 0 : 'unset',
+}));
+
+const Collapsed = styled('div')({
+ cursor: 'pointer',
+ display: 'inline-block',
+ color: '#B9CAD5',
+});
+
+const LastItemRadius = styled('div')({
+ boxSizing: 'content-box',
+ position: 'absolute',
+ left: '-0.5px',
+ top: 0,
+ height: ITEM_POINT_HEIGHT,
+ bottom: '50%',
+ width: '16px',
+ borderWidth: '1px',
+ borderStyle: 'solid',
+ borderLeftColor: TREE_COLOR,
+ borderBottomColor: TREE_COLOR,
+ borderTop: 'none',
+ borderRight: 'none',
+ borderRadius: '0 0 0 3px',
+ pointerEvents: 'none',
});
const StyledBorder = styled('div')({
diff --git a/libs/components/editor-core/src/contexts.tsx b/libs/components/editor-core/src/Contexts.tsx
similarity index 70%
rename from libs/components/editor-core/src/contexts.tsx
rename to libs/components/editor-core/src/Contexts.tsx
index cdd4dbf305..418fe0e8b4 100644
--- a/libs/components/editor-core/src/contexts.tsx
+++ b/libs/components/editor-core/src/Contexts.tsx
@@ -1,9 +1,8 @@
import { createContext, useContext } from 'react';
import type { BlockEditor, AsyncBlock } from './editor';
-import type { Column } from '@toeverything/datasource/db-service';
import { genErrorObj } from '@toeverything/utils';
-export const RootContext = createContext<{
+const RootContext = createContext<{
editor: BlockEditor;
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top
editorElement: () => JSX.Element;
@@ -14,6 +13,8 @@ export const RootContext = createContext<{
) as any
);
+export const EditorProvider = RootContext.Provider;
+
export const useEditor = () => {
return useContext(RootContext);
};
@@ -22,16 +23,3 @@ export const useEditor = () => {
* @deprecated
*/
export const BlockContext = createContext(null as any);
-
-/**
- * Context of column information
- *
- * @deprecated
- */
-export const ColumnsContext = createContext<{
- fromId: string;
- columns: Column[];
-}>({
- fromId: '',
- columns: [],
-});
diff --git a/libs/components/editor-core/src/RenderRoot.tsx b/libs/components/editor-core/src/RenderRoot.tsx
index aed9ebea50..e4534b813d 100644
--- a/libs/components/editor-core/src/RenderRoot.tsx
+++ b/libs/components/editor-core/src/RenderRoot.tsx
@@ -2,14 +2,14 @@ import type { BlockEditor } from './editor';
import { styled, usePatchNodes } from '@toeverything/components/ui';
import type { FC, PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState, useCallback } from 'react';
-import { RootContext } from './contexts';
+import { EditorProvider } from './Contexts';
import { SelectionRect, SelectionRef } from './Selection';
import {
Protocol,
services,
type ReturnUnobserve,
} from '@toeverything/datasource/db-service';
-import { addNewGroup } from './recast-block';
+import { addNewGroup, appendNewGroup } from './recast-block';
import { useIsOnDrag } from './hooks';
interface RenderRootProps {
@@ -151,7 +151,7 @@ export const RenderRoot: FC> = ({
};
return (
-
+
{
@@ -183,7 +183,7 @@ export const RenderRoot: FC> = ({
{editor.isWhiteboard ? null : }
{patchedNodes}
-
+
);
};
@@ -199,24 +199,32 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
mouseMoved.current = false;
return;
}
- const lastBlock = await editor.getRootLastChildrenBlock();
+ const rootBlock = await editor.getBlockById(
+ editor.getRootBlockId()
+ );
+ if (!rootBlock) {
+ throw new Error('root block is not found');
+ }
- const lastGroupBlock = await editor.getRootLastChildrenBlock();
+ const lastRootChildren = await rootBlock.lastChild();
// If last block is not a group
// create a group with a empty text
- if (lastGroupBlock.type !== 'group') {
- addNewGroup(editor, lastBlock, true);
+ if (lastRootChildren == null) {
+ appendNewGroup(editor, rootBlock, true);
return;
}
- if (lastGroupBlock.childrenIds.length > 1) {
- addNewGroup(editor, lastBlock, true);
+ if (
+ lastRootChildren.type !== Protocol.Block.Type.group ||
+ lastRootChildren.childrenIds.length > 1
+ ) {
+ addNewGroup(editor, lastRootChildren, true);
return;
}
// If the **only** block in the group is text and is empty
// active the text block
- const theGroupChildBlock = await lastGroupBlock.firstChild();
+ const theGroupChildBlock = await lastRootChildren.firstChild();
if (
theGroupChildBlock &&
@@ -229,7 +237,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
return;
}
// else create a new group
- addNewGroup(editor, lastBlock, true);
+ addNewGroup(editor, lastRootChildren, true);
},
[editor]
);
diff --git a/libs/components/editor-core/src/block-content-wrapper/BlockContentWrapper.tsx b/libs/components/editor-core/src/block-content-wrapper/BlockContentWrapper.tsx
deleted file mode 100644
index 792b1a8455..0000000000
--- a/libs/components/editor-core/src/block-content-wrapper/BlockContentWrapper.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { AsyncBlock, BlockEditor } from '../editor';
-import type { FC, ReactElement } from 'react';
-import { BlockPendantProvider } from '../block-pendant';
-import { DragDropWrapper } from '../drag-drop-wrapper';
-
-type BlockContentWrapperProps = {
- block: AsyncBlock;
- editor: BlockEditor;
- children: ReactElement | null;
-};
-
-export const WrapperWithPendantAndDragDrop: FC =
- function ({ block, children, editor }) {
- return (
-
-
- {children}
-
-
- );
- };
diff --git a/libs/components/editor-core/src/block-content-wrapper/index.ts b/libs/components/editor-core/src/block-content-wrapper/index.ts
deleted file mode 100644
index 80c770d496..0000000000
--- a/libs/components/editor-core/src/block-content-wrapper/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './BlockContentWrapper';
diff --git a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx
index 877c4e451a..66cf4001a0 100644
--- a/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx
+++ b/libs/components/editor-core/src/block-pendant/BlockPendantProvider.tsx
@@ -1,5 +1,4 @@
import type { FC, PropsWithChildren } from 'react';
-import React, { useState } from 'react';
import { styled } from '@toeverything/components/ui';
import type { AsyncBlock } from '../editor';
import { PendantPopover } from './pendant-popover';
@@ -11,74 +10,68 @@ interface BlockTagProps {
block: AsyncBlock;
}
-/**
- * @deprecated Need to be refactored
- */
export const BlockPendantProvider: FC> = ({
block,
children,
}) => {
- const [container, setContainer] = useState(null);
- const [isHover, setIsHover] = useState(false);
return (
- setContainer(dom)}>
+
{children}
- {container && (
- {
- setIsHover(visible);
- }}
- >
-
-
- )}
+
+
+
+
+
);
};
-const Container = styled('div')({
+export const LINE_GAP = 16;
+const TAG_GAP = 4;
+
+const StyledTriggerLine = styled('div')({
+ padding: `${TAG_GAP}px 0`,
+ width: '100px',
+ cursor: 'default',
+ display: 'flex',
+ alignItems: 'flex-end',
position: 'relative',
- padding: '4px',
- '&:hover .triggerLine::before': {
- display: 'flex',
+
+ '::before': {
+ content: "''",
+ width: '100%',
+ height: '2px',
+ background: '#dadada',
+ display: 'none',
+ position: 'absolute',
+ left: '0',
+ top: '4px',
+ },
+ '::after': {
+ content: "''",
+ width: '0',
+ height: '2px',
+ background: '#aac4d5',
+ display: 'block',
+ position: 'absolute',
+ left: '0',
+ top: '4px',
+ transition: 'width .3s',
},
});
-const StyledTriggerLine = styled('div')<{ isHover: boolean }>(({ isHover }) => {
- return {
- padding: '4px 0',
- width: '100px',
- cursor: 'default',
- display: 'flex',
- alignItems: 'flex-end',
- position: 'relative',
-
- '::before': {
- content: "''",
- width: '100%',
- height: '2px',
- background: '#dadada',
- display: 'none',
- position: 'absolute',
- left: '0',
- top: '4px',
+const Container = styled('div')({
+ position: 'relative',
+ paddingBottom: `${LINE_GAP - TAG_GAP * 2}px`,
+ '&:hover': {
+ [StyledTriggerLine.toString()]: {
+ '&::before': {
+ display: 'flex',
+ },
+ '&::after': {
+ width: '100%',
+ },
},
- '::after': {
- content: "''",
- width: isHover ? '100%' : '0',
- height: '2px',
- background: '#aac4d5',
- display: 'block',
- position: 'absolute',
- left: '0',
- top: '4px',
- transition: 'width .3s',
- },
- };
+ },
});
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 5fdd24788c..0f610301df 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
@@ -1,5 +1,4 @@
import React, { ReactNode, useRef, useEffect, useState } from 'react';
-import { getPendantHistory } from '../utils';
import {
getRecastItemValue,
RecastMetaProperty,
@@ -30,22 +29,22 @@ export const PendantHistoryPanel = ({
const [history, setHistory] = useState([]);
const popoverHandlerRef = useRef<{ [key: string]: PopperHandler }>({});
+ const { getValueHistory } = getRecastItemValue(block);
useEffect(() => {
const init = async () => {
const currentBlockValues = getRecastItemValue(block).getAllValue();
- const allProperties = getProperties();
- const missProperties = allProperties.filter(
+ const missValues = getProperties().filter(
property => !currentBlockValues.find(v => v.id === property.id)
);
- const pendantHistory = getPendantHistory({
+ const valueHistory = getValueHistory({
recastBlockId: recastBlock.id,
});
- const historyMap = missProperties.reduce<{
- [key: RecastPropertyId]: string;
+ const historyMap = missValues.reduce<{
+ [key: RecastPropertyId]: string[];
}>((history, property) => {
- if (pendantHistory[property.id]) {
- history[property.id] = pendantHistory[property.id];
+ if (valueHistory[property.id]) {
+ history[property.id] = valueHistory[property.id];
}
return history;
@@ -54,18 +53,30 @@ export const PendantHistoryPanel = ({
const blockHistory = (
await Promise.all(
Object.entries(historyMap).map(
- async ([propertyId, blockId]) => {
- const latestValueBlock = (
- await groupBlock.children()
- ).find((block: AsyncBlock) => block.id === blockId);
+ async ([propertyId, blockIds]) => {
+ const blocks = await groupBlock.children();
+ const latestChangeBlock = blockIds
+ .reverse()
+ .reduce((block, id) => {
+ if (!block) {
+ return blocks.find(
+ block => block.id === id
+ );
+ }
+ return block;
+ }, null);
- return getRecastItemValue(
- latestValueBlock
- ).getValue(propertyId as RecastPropertyId);
+ if (latestChangeBlock) {
+ return getRecastItemValue(
+ latestChangeBlock
+ ).getValue(propertyId as RecastPropertyId);
+ }
+ return null;
}
)
)
).filter(v => v);
+
setHistory(blockHistory);
};
diff --git a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Information.tsx b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Information.tsx
index afd5bc5525..a8929f9e6b 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Information.tsx
+++ b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Information.tsx
@@ -4,7 +4,7 @@ import { ModifyPanelContentProps } from './types';
import { StyledDivider, StyledPopoverSubTitle } from '../StyledComponent';
import { BasicSelect } from './Select';
import { InformationProperty, InformationValue } from '../../recast-block';
-import { genInitialOptions, getPendantIconsConfigByName } from '../utils';
+import { generateInitialOptions, getPendantIconsConfigByName } from '../utils';
export default (props: ModifyPanelContentProps) => {
const { onPropertyChange, onValueChange, initialValue, property } = props;
@@ -38,7 +38,7 @@ export default (props: ModifyPanelContentProps) => {
}}
initialOptions={
propProperty?.emailOptions ||
- genInitialOptions(
+ generateInitialOptions(
property?.type,
getPendantIconsConfigByName('Email')
)
@@ -66,7 +66,7 @@ export default (props: ModifyPanelContentProps) => {
}}
initialOptions={
propProperty?.phoneOptions ||
- genInitialOptions(
+ generateInitialOptions(
property?.type,
getPendantIconsConfigByName('Phone')
)
@@ -94,7 +94,7 @@ export default (props: ModifyPanelContentProps) => {
}}
initialOptions={
propProperty?.locationOptions ||
- genInitialOptions(
+ generateInitialOptions(
property?.type,
getPendantIconsConfigByName('Location')
)
diff --git a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx
index 0b741ecbdc..8ce2bab003 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx
+++ b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Mention.tsx
@@ -18,7 +18,9 @@ export default ({
user: { username, nickname, photo },
} = useUserAndSpaces();
- const [selectedValue, setSelectedValue] = useState(initialValue?.value);
+ const [selectedValue, setSelectedValue] = useState(
+ initialValue?.value || ''
+ );
const [focus, setFocus] = useState(false);
const theme = useTheme();
return (
diff --git a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Select.tsx b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Select.tsx
index 6d0d1f26f9..b016437ac8 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Select.tsx
+++ b/libs/components/editor-core/src/block-pendant/pendant-modify-panel/Select.tsx
@@ -21,7 +21,7 @@ import {
} from '@toeverything/components/ui';
import { HighLightIconInput } from './IconInput';
import { PendantConfig, IconNames, OptionIdType, OptionType } from '../types';
-import { genBasicOption } from '../utils';
+import { generateBasicOption } from '../utils';
type OptionItemType = {
option: OptionType;
@@ -66,7 +66,7 @@ export const BasicSelect = ({
const [selectIds, setSelectIds] = useState(initialValue);
const insertOption = (insertId: OptionIdType) => {
- const newOption = genBasicOption({
+ const newOption = generateBasicOption({
index: options.length + 1,
iconConfig,
});
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 b9d29cebe7..b09f23ed35 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx
+++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/CreatePendantPanel.tsx
@@ -1,5 +1,4 @@
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';
@@ -15,13 +14,13 @@ import {
StyledPopoverSubTitle,
StyledPopoverWrapper,
} from '../StyledComponent';
-import { genInitialOptions, getPendantConfigByType } from '../utils';
+import {
+ generateRandomFieldName,
+ generateInitialOptions,
+ getPendantConfigByType,
+} from '../utils';
import { useOnCreateSure } from './hooks';
-const upperFirst = (str: string) => {
- return `${str[0].toUpperCase()}${str.slice(1)}`;
-};
-
export const CreatePendantPanel = ({
block,
onSure,
@@ -35,7 +34,7 @@ export const CreatePendantPanel = ({
useEffect(() => {
selectedOption &&
- setFieldName(upperFirst(`${selectedOption.type}#${nanoid(4)}`));
+ setFieldName(generateRandomFieldName(selectedOption.type));
}, [selectedOption]);
return (
@@ -45,7 +44,7 @@ export const CreatePendantPanel = ({
{
setSelectedOption(selectedValue);
}}
@@ -93,7 +92,7 @@ export const CreatePendantPanel = ({
{
const pendantOption = pendantOptions.find(v => v.type === property.type);
const iconConfig = getPendantConfigByType(property.type);
- const { removePendant } = usePendant(block);
+ const { removeValue } = getRecastItemValue(block);
+
const Icon = IconMap[iconConfig.iconName];
const [fieldName, setFieldName] = useState(property.name);
const onUpdateSure = useOnUpdateSure({ block, property });
@@ -108,7 +109,7 @@ export const UpdatePendantPanel = ({
onDelete={
hasDelete
? async () => {
- await removePendant(property);
+ await removeValue(property.id);
}
: null
}
diff --git a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts
index 83889962ac..079cb26277 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts
+++ b/libs/components/editor-core/src/block-pendant/pendant-operation-panel/hooks.ts
@@ -1,16 +1,23 @@
import type { CSSProperties } from 'react';
import {
genSelectOptionId,
+ getRecastItemValue,
type InformationProperty,
type MultiSelectProperty,
type RecastMetaProperty,
type SelectOption,
type SelectProperty,
+ useRecastBlock,
useRecastBlockMeta,
useSelectProperty,
+ SelectValue,
+ MultiSelectValue,
+ StatusValue,
+ InformationValue,
+ TextValue,
+ DateValue,
} from '../../recast-block';
import { type AsyncBlock } from '../../editor';
-import { usePendant } from '../use-pendant';
import {
type OptionType,
PendantTypes,
@@ -41,8 +48,8 @@ const genOptionWithId = (options: OptionType[] = []) => {
export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
const { addProperty } = useRecastBlockMeta();
const { createSelect } = useSelectProperty();
- const { setPendant } = usePendant(block);
-
+ const recastBlock = useRecastBlock();
+ const { setValue } = getRecastItemValue(block);
return async ({
type,
fieldName,
@@ -79,7 +86,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
tempSelectedId: newValue,
});
- await setPendant(newProperty, selectedId);
+ await setValue(
+ {
+ id: newProperty.id,
+ type: newProperty.type,
+ value: selectedId,
+ } as SelectValue | MultiSelectValue | StatusValue,
+ recastBlock.id
+ );
} else if (type === PendantTypes.Information) {
const emailOptions = genOptionWithId(newPropertyItem.emailOptions);
@@ -97,26 +111,33 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
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,
- }),
- });
+ await setValue(
+ {
+ id: newProperty.id,
+ type: newProperty.type,
+ value: {
+ 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,
+ }),
+ },
+ } as InformationValue,
+ recastBlock.id
+ );
} else {
// TODO: Color and background should use pendant config, but ui is not design now
const iconConfig = getPendantConfigByType(type);
@@ -129,8 +150,14 @@ export const useOnCreateSure = ({ block }: { block: AsyncBlock }) => {
color: iconConfig.color as CSSProperties['color'],
iconName: iconConfig.iconName,
});
-
- await setPendant(newProperty, newValue);
+ await setValue(
+ {
+ id: newProperty.id,
+ type: newProperty.type,
+ value: newValue,
+ } as TextValue | DateValue,
+ recastBlock.id
+ );
}
};
};
@@ -144,8 +171,9 @@ export const useOnUpdateSure = ({
property: RecastMetaProperty;
}) => {
const { updateSelect } = useSelectProperty();
- const { setPendant } = usePendant(block);
const { updateProperty } = useRecastBlockMeta();
+ const { setValue } = getRecastItemValue(block);
+ const recastBlock = useRecastBlock();
return async ({
type,
@@ -199,7 +227,14 @@ export const useOnUpdateSure = ({
tempSelectedId: newValue,
});
- await setPendant(selectProperty, selectedId);
+ await setValue(
+ {
+ id: selectProperty.id,
+ type: selectProperty.type,
+ value: selectedId,
+ } as SelectValue | MultiSelectValue | StatusValue,
+ recastBlock.id
+ );
} else if (type === PendantTypes.Information) {
// const { emailOptions, phoneOptions, locationOptions } =
// property as InformationProperty;
@@ -231,28 +266,42 @@ export const useOnUpdateSure = ({
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,
- }),
- });
+ await setValue(
+ {
+ id: newProperty.id,
+ type: newProperty.type,
+ value: {
+ 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,
+ }),
+ },
+ } as InformationValue,
+ recastBlock.id
+ );
} else {
- await setPendant(property, newValue);
+ await setValue(
+ {
+ id: property.id,
+ type: property.type,
+ value: newValue,
+ } as TextValue | DateValue,
+ recastBlock.id
+ );
}
if (fieldName !== property.name) {
diff --git a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx
index cdc06c07d9..70cb5115c6 100644
--- a/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx
+++ b/libs/components/editor-core/src/block-pendant/pendant-popover/PendantPopover.tsx
@@ -1,4 +1,4 @@
-import React, { FC, useRef } from 'react';
+import { FC, useRef } from 'react';
import { AsyncBlock } from '../../editor';
import { PendantHistoryPanel } from '../pendant-history-panel';
import {
@@ -21,8 +21,6 @@ export const PendantPopover: FC<
pointerEnterDelay={300}
pointerLeaveDelay={200}
placement="bottom-start"
- // visible={true}
- // trigger="click"
content={
{
);
})}
{hasAddBtn ? (
-
+
-
+
) : null}
);
diff --git a/libs/components/editor-core/src/block-pendant/use-pendant.ts b/libs/components/editor-core/src/block-pendant/use-pendant.ts
deleted file mode 100644
index 55c8aa054e..0000000000
--- a/libs/components/editor-core/src/block-pendant/use-pendant.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { removePropertyValueRecord, setPendantHistory } from './utils';
-import { AsyncBlock } from '../editor';
-import {
- getRecastItemValue,
- RecastMetaProperty,
- useRecastBlock,
-} from '../recast-block';
-
-export const usePendant = (block: AsyncBlock) => {
- // const { getProperties, removeProperty } = useRecastBlockMeta();
- const recastBlock = useRecastBlock();
- const { getValue, setValue, removeValue } = getRecastItemValue(block);
- // const { updateSelect } = useSelectProperty();
-
- const setPendant = async (property: RecastMetaProperty, newValue: any) => {
- const nv = {
- id: property.id,
- type: property.type,
- value: newValue,
- };
- await setValue(nv);
- setPendantHistory({
- recastBlockId: recastBlock.id,
- blockId: block.id,
- propertyId: property.id,
- });
- };
-
- const removePendant = async (property: RecastMetaProperty) => {
- await removeValue(property.id);
- removePropertyValueRecord({
- recastBlockId: block.id,
- propertyId: property.id,
- });
- };
-
- return {
- setPendant,
- removePendant,
- };
-};
diff --git a/libs/components/editor-core/src/block-pendant/utils.ts b/libs/components/editor-core/src/block-pendant/utils.ts
index d2807dbb85..63a3344a91 100644
--- a/libs/components/editor-core/src/block-pendant/utils.ts
+++ b/libs/components/editor-core/src/block-pendant/utils.ts
@@ -1,84 +1,7 @@
-import {
- PropertyType,
- RecastBlockValue,
- RecastPropertyId,
- SelectOption,
-} from '../recast-block';
-import { OptionIdType, OptionType } from './types';
+import { PropertyType, SelectOption } from '../recast-block';
+import { OptionIdType, OptionType, PendantConfig, PendantTypes } from './types';
import { pendantConfig } from './config';
-import { PendantConfig, PendantTypes } from './types';
-type Props = {
- recastBlockId: string;
- blockId: string;
- propertyId: RecastPropertyId;
-};
-
-type StorageMap = {
- [recastBlockId: string]: {
- [propertyId: RecastPropertyId]: string;
- };
-};
-
-const LOCAL_STORAGE_NAME = 'TEMPORARY_PENDANT_DATA';
-
-const ensureLocalStorage = () => {
- const data = localStorage.getItem(LOCAL_STORAGE_NAME);
- if (!data) {
- localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({}));
- }
-};
-
-export const setPendantHistory = ({
- recastBlockId,
- blockId,
- propertyId,
-}: Props) => {
- ensureLocalStorage();
- const data: StorageMap = JSON.parse(
- localStorage.getItem(LOCAL_STORAGE_NAME) as string
- );
-
- if (!data[recastBlockId]) {
- data[recastBlockId] = {};
- }
- const propertyValueRecord = data[recastBlockId];
- propertyValueRecord[propertyId] = blockId;
-
- localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
-};
-
-export const getPendantHistory = ({
- recastBlockId,
-}: {
- recastBlockId: string;
-}) => {
- ensureLocalStorage();
- const data: StorageMap = JSON.parse(
- localStorage.getItem(LOCAL_STORAGE_NAME) as string
- );
-
- return data[recastBlockId] ?? {};
-};
-
-export const removePropertyValueRecord = ({
- recastBlockId,
- propertyId,
-}: {
- recastBlockId: string;
- propertyId: RecastPropertyId;
-}) => {
- ensureLocalStorage();
- const data: StorageMap = JSON.parse(
- localStorage.getItem(LOCAL_STORAGE_NAME) as string
- );
- if (!data[recastBlockId]) {
- return;
- }
-
- delete data[recastBlockId][propertyId];
-
- localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
-};
+import { nanoid } from 'nanoid';
/**
* In select pendant panel, use mock options instead of use `createSelect` when add or delete option
@@ -107,7 +30,7 @@ export const getOfficialSelected = ({
.map(id => {
return tempOptions.findIndex((o: OptionType) => o.id === id);
})
- .filter(index => index != -1);
+ .filter(index => index !== -1);
selectedId = selectedIndex.map((index: number) => {
return options[index].id;
});
@@ -130,7 +53,7 @@ export const getPendantIconsConfigByName = (
return pendantConfig[pendantName];
};
-export const genBasicOption = ({
+export const generateBasicOption = ({
index,
iconConfig,
name = '',
@@ -159,22 +82,22 @@ export const genBasicOption = ({
/**
* Status Pendant is a Select Pendant built-in some options
* **/
-export const genInitialOptions = (
+export const generateInitialOptions = (
type: PendantTypes,
iconConfig: PendantConfig
) => {
if (type === PendantTypes.Status) {
return [
- genBasicOption({ index: 0, iconConfig, name: 'No Started' }),
- genBasicOption({
+ generateBasicOption({ index: 0, iconConfig, name: 'No Started' }),
+ generateBasicOption({
index: 1,
iconConfig,
name: 'In Progress',
}),
- genBasicOption({ index: 2, iconConfig, name: 'Complete' }),
+ generateBasicOption({ index: 2, iconConfig, name: 'Complete' }),
];
}
- return [genBasicOption({ index: 0, iconConfig })];
+ return [generateBasicOption({ index: 0, iconConfig })];
};
export const checkPendantForm = (
@@ -222,3 +145,10 @@ export const checkPendantForm = (
return { passed: true, message: 'Check passed !' };
};
+
+const upperFirst = (str: string) => {
+ return `${str[0].toUpperCase()}${str.slice(1)}`;
+};
+
+export const generateRandomFieldName = (type: PendantTypes) =>
+ upperFirst(`${type}#${nanoid(4)}`);
diff --git a/libs/components/editor-core/src/drag-drop-wrapper/DragDropWrapper.tsx b/libs/components/editor-core/src/drag-drop-wrapper/DragDropWrapper.tsx
deleted file mode 100644
index 030c2b5c20..0000000000
--- a/libs/components/editor-core/src/drag-drop-wrapper/DragDropWrapper.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { AsyncBlock, BlockEditor } from '../editor';
-import { ReactElement } from 'react';
-
-interface DragDropWrapperProps {
- editor: BlockEditor;
- block: AsyncBlock;
- children: ReactElement | null;
-}
-
-export function DragDropWrapper({
- children,
- editor,
- block,
-}: DragDropWrapperProps) {
- const handlerDragOver: React.DragEventHandler = event => {
- event.preventDefault();
- if (block.dom) {
- editor.getHooks().afterOnNodeDragOver(event, {
- blockId: block.id,
- dom: block.dom,
- rect: block.dom?.getBoundingClientRect(),
- type: block.type,
- properties: block.getProperties(),
- });
- }
- };
- return {children}
;
-}
diff --git a/libs/components/editor-core/src/drag-drop-wrapper/index.ts b/libs/components/editor-core/src/drag-drop-wrapper/index.ts
deleted file mode 100644
index bfade20431..0000000000
--- a/libs/components/editor-core/src/drag-drop-wrapper/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './DragDropWrapper';
diff --git a/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts b/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts
index d3160d56f5..35426c73de 100644
--- a/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts
+++ b/libs/components/editor-core/src/editor/drag-drop/drag-drop.ts
@@ -12,6 +12,7 @@ enum DragType {
}
const DRAG_STATE_CHANGE_EVENT_KEY = 'dragStateChange';
+const MAX_GRID_BLOCK_FLOOR = 3;
export class DragDropManager {
private _editor: Editor;
private _enabled: boolean;
@@ -231,6 +232,17 @@ export class DragDropManager {
if (!(await this._canBeDrop(event))) {
direction = BlockDropPlacement.none;
}
+ if (
+ direction === BlockDropPlacement.left ||
+ direction === BlockDropPlacement.right
+ ) {
+ const path = await this._editor.getBlockPath(blockId);
+ const gridBlocks = path.filter(block => block.type === 'grid');
+ // limit grid block floor counts
+ if (gridBlocks.length >= MAX_GRID_BLOCK_FLOOR) {
+ direction = BlockDropPlacement.none;
+ }
+ }
this._setBlockDragDirection(direction);
return direction;
}
diff --git a/libs/components/editor-core/src/editor/editor.ts b/libs/components/editor-core/src/editor/editor.ts
index 73e7f6f7d5..208c4e9ef1 100644
--- a/libs/components/editor-core/src/editor/editor.ts
+++ b/libs/components/editor-core/src/editor/editor.ts
@@ -340,7 +340,20 @@ export class Editor implements Virgo {
const rootBlockId = this.getRootBlockId();
const rootBlock = await this.getBlockById(rootBlockId);
const blockList: Array = rootBlock ? [rootBlock] : [];
- const children = (await rootBlock?.children()) || [];
+ return [...blockList, ...(await this.getOffspring(rootBlockId))];
+ }
+
+ /**
+ *
+ * get all offspring of block
+ * @param {string} id
+ * @return {*}
+ * @memberof Editor
+ */
+ async getOffspring(id: string) {
+ const block = await this.getBlockById(id);
+ const blockList: Array = [];
+ const children = (await block?.children()) || [];
for (const block of children) {
if (!block) {
continue;
@@ -354,15 +367,6 @@ export class Editor implements Virgo {
return blockList;
}
- async getRootLastChildrenBlock(rootBlockId = this.getRootBlockId()) {
- const rootBlock = await this.getBlockById(rootBlockId);
- if (!rootBlock) {
- throw new Error('root block is not found');
- }
- const lastChildren = await rootBlock.lastChild();
- return lastChildren ?? rootBlock;
- }
-
async getLastBlock(rootBlockId = this.getRootBlockId()) {
const rootBlock = await this.getBlockById(rootBlockId);
if (!rootBlock) {
@@ -379,6 +383,20 @@ export class Editor implements Virgo {
return lastBlock;
}
+ async getBlockPath(id: string) {
+ const block = await this.getBlockById(id);
+ if (!block) {
+ return [];
+ }
+ const path = [block];
+ let parent = await block.parent();
+ while (parent) {
+ path.unshift(parent);
+ parent = await parent.parent();
+ }
+ return path;
+ }
+
async getBlockByPoint(point: Point) {
const blockList = await this.getBlockList();
diff --git a/libs/components/editor-core/src/editor/keyboard/keyboard.ts b/libs/components/editor-core/src/editor/keyboard/keyboard.ts
index 751e888588..04d663ea30 100644
--- a/libs/components/editor-core/src/editor/keyboard/keyboard.ts
+++ b/libs/components/editor-core/src/editor/keyboard/keyboard.ts
@@ -35,20 +35,6 @@ export class KeyboardManager {
}
this.handler_map = {};
- // WARNING: Remove the filter of hotkeys, the input event of input/select/textarea will be filtered out by default
- // When there is a problem with the input of the text component, you need to pay attention to this
- const old_filter = HotKeys.filter;
- HotKeys.filter = event => {
- let parent = (event.target as Element).parentElement;
- while (parent) {
- if (parent === editor.container) {
- return old_filter(event);
- }
- parent = parent.parentElement;
- }
-
- return true;
- };
HotKeys.setScope('editor');
// this.init_common_shortcut_cb();
diff --git a/libs/components/editor-core/src/editor/plugin/hooks.ts b/libs/components/editor-core/src/editor/plugin/hooks.ts
index 1c05308a84..45cecebc0f 100644
--- a/libs/components/editor-core/src/editor/plugin/hooks.ts
+++ b/libs/components/editor-core/src/editor/plugin/hooks.ts
@@ -113,13 +113,6 @@ export class Hooks implements HooksRunner, PluginHooks {
this._runHook(HookType.ON_ROOTNODE_DRAG_OVER_CAPTURE, e);
}
- public afterOnNodeDragOver(
- e: React.DragEvent,
- node: BlockDomInfo
- ): void {
- this._runHook(HookType.AFTER_ON_NODE_DRAG_OVER, e, node);
- }
-
public onSearch(): void {
this._runHook(HookType.ON_SEARCH);
}
diff --git a/libs/components/editor-core/src/editor/scroll/scroll.ts b/libs/components/editor-core/src/editor/scroll/scroll.ts
index 81f66578e4..2b2f1d114b 100644
--- a/libs/components/editor-core/src/editor/scroll/scroll.ts
+++ b/libs/components/editor-core/src/editor/scroll/scroll.ts
@@ -30,7 +30,6 @@ export class ScrollManager {
constructor(editor: BlockEditor) {
this._editor = editor;
- (window as any).scrollManager = this;
}
private _updateScrollInfo(left: number, top: number) {
@@ -111,6 +110,7 @@ export class ScrollManager {
}
public emitScrollEvent(event: UIEvent) {
+ this.scrollContainer = event.target as HTMLElement;
this._scrollDirection = this._getScrollDirection();
this._scrollMoveOffset = Math.abs(
this.scrollContainer.scrollTop - this._scrollRecord[0]
diff --git a/libs/components/editor-core/src/editor/types.ts b/libs/components/editor-core/src/editor/types.ts
index 797d68f46c..ca22ae5d4e 100644
--- a/libs/components/editor-core/src/editor/types.ts
+++ b/libs/components/editor-core/src/editor/types.ts
@@ -177,7 +177,6 @@ export enum HookType {
ON_ROOTNODE_DRAG_END = 'onRootNodeDragEnd',
ON_ROOTNODE_DRAG_OVER_CAPTURE = 'onRootNodeDragOverCapture',
ON_ROOTNODE_DROP = 'onRootNodeDrop',
- AFTER_ON_NODE_DRAG_OVER = 'afterOnNodeDragOver',
BEFORE_COPY = 'beforeCopy',
BEFORE_CUT = 'beforeCut',
ON_ROOTNODE_SCROLL = 'onRootNodeScroll',
@@ -219,10 +218,6 @@ export interface HooksRunner {
onRootNodeDragEnd: (e: React.DragEvent) => void;
onRootNodeDragLeave: (e: React.DragEvent) => void;
onRootNodeDrop: (e: React.DragEvent) => void;
- afterOnNodeDragOver: (
- e: React.DragEvent,
- node: BlockDomInfo
- ) => void;
beforeCopy: (e: ClipboardEvent) => void;
beforeCut: (e: ClipboardEvent) => void;
onRootNodeScroll: (e: React.UIEvent) => void;
diff --git a/libs/components/editor-core/src/hooks.ts b/libs/components/editor-core/src/hooks.ts
index d7d1a00cef..f0af8a0f87 100644
--- a/libs/components/editor-core/src/hooks.ts
+++ b/libs/components/editor-core/src/hooks.ts
@@ -1,3 +1,6 @@
+import { noop, Point } from '@toeverything/utils';
+import { useCallback, useEffect, useRef, useState } from 'react';
+import { useEditor } from './Contexts';
import {
AsyncBlock,
BlockEditor,
@@ -5,9 +8,6 @@ import {
SelectionInfo,
SelectionSettingsMap,
} from './editor';
-import { noop, Point } from '@toeverything/utils';
-import { useCallback, useContext, useEffect, useRef, useState } from 'react';
-import { RootContext } from './contexts';
function useRequestReRender() {
const [, setUpdateCounter] = useState(0);
@@ -56,7 +56,7 @@ function useRequestReRender() {
export const useBlock = (blockId: string) => {
const [block, setBlock] = useState();
const requestReRender = useRequestReRender();
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
if (!blockId) {
return undefined;
@@ -95,7 +95,7 @@ export const useOnSelect = (
blockId: string,
cb: (isSelect: boolean) => void
) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
return () => {
@@ -117,7 +117,7 @@ export const useOnSelectActive = (
blockId: string,
cb: (position: Point | undefined) => void
) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
return () => {
@@ -139,7 +139,7 @@ export const useOnSelectSetSelection = (
blockId: string,
cb: (args: SelectionSettingsMap[T]) => void
) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.observe(
blockId,
@@ -162,7 +162,7 @@ export const useOnSelectSetSelection = (
* @export
*/
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.onSelectionChange(cb);
return () => {
@@ -177,7 +177,7 @@ export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
* @export
*/
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.selectionManager.onSelectEnd(cb);
return () => {
@@ -195,7 +195,7 @@ export const useOnSelectStartWith = (
blockId: string,
cb: (args: MouseEvent) => void
) => {
- const { editor } = useContext(RootContext);
+ const { editor } = useEditor();
useEffect(() => {
editor.mouseManager.onSelectStartWith(blockId, cb);
return () => {
diff --git a/libs/components/editor-core/src/index.ts b/libs/components/editor-core/src/index.ts
index 64939c0659..bea2fed3a0 100644
--- a/libs/components/editor-core/src/index.ts
+++ b/libs/components/editor-core/src/index.ts
@@ -1,4 +1,3 @@
-export { ColumnsContext, RootContext } from './contexts';
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
export * from './render-block';
export * from './hooks';
@@ -15,7 +14,6 @@ export * from './kanban/types';
export * from './utils';
-export * from './drag-drop-wrapper';
-export * from './block-content-wrapper';
-
export * from './editor';
+
+export { RefPageProvider, useRefPage } from './ref-page';
diff --git a/libs/components/editor-core/src/kanban/atom.ts b/libs/components/editor-core/src/kanban/atom.ts
index b903e2d92f..86858a559e 100644
--- a/libs/components/editor-core/src/kanban/atom.ts
+++ b/libs/components/editor-core/src/kanban/atom.ts
@@ -6,10 +6,15 @@ import {
PropertyType,
RecastBlockValue,
RecastMetaProperty,
- RecastPropertyId,
} from '../recast-block/types';
import type { DefaultGroup, KanbanGroup } from './types';
import { DEFAULT_GROUP_ID } from './types';
+import {
+ generateInitialOptions,
+ generateRandomFieldName,
+ getPendantIconsConfigByName,
+} from '../block-pendant/utils';
+import { SelectOption } from '../recast-block';
/**
* - If the `groupBy` is `SelectProperty` or `MultiSelectProperty`, return `(Multi)SelectProperty.options`.
@@ -23,6 +28,7 @@ export const getGroupOptions = async (
return [];
}
switch (groupBy.type) {
+ case PropertyType.Status:
case PropertyType.Select:
case PropertyType.MultiSelect: {
return groupBy.options.map(option => ({
@@ -51,15 +57,13 @@ const isValueBelongOption = (
option: KanbanGroup
) => {
switch (propertyValue.type) {
- case PropertyType.Select: {
+ case PropertyType.Select:
+ case PropertyType.Status: {
return propertyValue.value === option.id;
}
case PropertyType.MultiSelect: {
return propertyValue.value.some(i => i === option.id);
}
- // case PropertyType.Text: {
- // TOTODO:DO support this type
- // }
default: {
console.error(propertyValue, option);
throw new Error('Not support group by type');
@@ -96,40 +100,67 @@ export const calcCardGroup = (
/**
* Set group value for the card block
*/
-export const moveCardToGroup = async (
- groupById: RecastPropertyId,
- cardBlock: RecastItem,
- group: KanbanGroup
-) => {
+export const moveCardToGroup = async ({
+ groupBy,
+ cardBlock,
+ group,
+ recastBlock,
+}: {
+ groupBy: RecastMetaProperty;
+ cardBlock: RecastItem;
+ group: KanbanGroup;
+ recastBlock: RecastBlock;
+}) => {
const { setValue, removeValue } = getRecastItemValue(cardBlock);
let success = false;
if (group.id === DEFAULT_GROUP_ID) {
- success = await removeValue(groupById);
+ success = await removeValue(groupBy.id);
return false;
}
+
switch (group.type) {
case PropertyType.Select: {
- success = await setValue({
- id: groupById,
- type: group.type,
- value: group.id,
- });
+ success = await setValue(
+ {
+ id: groupBy.id,
+ type: group.type,
+ value: group.id,
+ },
+ recastBlock.id
+ );
+ break;
+ }
+ case PropertyType.Status: {
+ success = await setValue(
+ {
+ id: groupBy.id,
+ type: group.type,
+ value: group.id,
+ },
+ recastBlock.id
+ );
break;
}
case PropertyType.MultiSelect: {
- success = await setValue({
- id: groupById,
- type: group.type,
- value: [group.id],
- });
+ success = await setValue(
+ {
+ id: groupBy.id,
+ type: group.type,
+ value: [group.id],
+ },
+ recastBlock.id
+ );
break;
}
case PropertyType.Text: {
- success = await setValue({
- id: groupById,
- type: group.type,
- value: group.id,
- });
+ success = await setValue(
+ {
+ id: groupBy.id,
+ type: group.type,
+ value: group.id,
+ },
+ recastBlock.id
+ );
break;
}
default:
@@ -194,14 +225,18 @@ export const genDefaultGroup = (groupBy: RecastMetaProperty): DefaultGroup => ({
items: [],
});
-export const DEFAULT_GROUP_BY_PROPERTY = {
- name: 'Status',
- options: [
- { name: 'No Started', color: '#E53535', background: '#FFCECE' },
- { name: 'In Progress', color: '#A77F1A', background: '#FFF5AB' },
- { name: 'Complete', color: '#3C8867', background: '#C5FBE0' },
- ],
-};
+export const generateDefaultGroupByProperty = (): {
+ name: string;
+ options: Omit[];
+ type: PropertyType.Status;
+} => ({
+ name: generateRandomFieldName(PropertyType.Status),
+ type: PropertyType.Status,
+ options: generateInitialOptions(
+ PropertyType.Status,
+ getPendantIconsConfigByName(PropertyType.Status)
+ ),
+});
/**
* Unwrap blocks from the grid recursively.
diff --git a/libs/components/editor-core/src/kanban/group.ts b/libs/components/editor-core/src/kanban/group.ts
index 0297946d89..e857a6865a 100644
--- a/libs/components/editor-core/src/kanban/group.ts
+++ b/libs/components/editor-core/src/kanban/group.ts
@@ -7,6 +7,7 @@ export const useKanbanGroup = (groupBy: RecastMetaProperty) => {
const { updateSelect } = useSelectProperty();
switch (groupBy.type) {
+ case PropertyType.Status:
case PropertyType.MultiSelect:
case PropertyType.Select: {
const {
diff --git a/libs/components/editor-core/src/kanban/kanban.ts b/libs/components/editor-core/src/kanban/kanban.ts
index 675e75db3e..218476ac44 100644
--- a/libs/components/editor-core/src/kanban/kanban.ts
+++ b/libs/components/editor-core/src/kanban/kanban.ts
@@ -1,6 +1,6 @@
import { Protocol } from '@toeverything/datasource/db-service';
import { useCallback, useContext, useEffect, useState } from 'react';
-import { useEditor } from '../contexts';
+import { useEditor } from '../Contexts';
import { AsyncBlock } from '../editor';
import { useRecastView } from '../recast-block';
import { useRecastBlock } from '../recast-block/Context';
@@ -18,8 +18,8 @@ import {
import { supportChildren } from '../utils';
import {
calcCardGroup,
- DEFAULT_GROUP_BY_PROPERTY,
genDefaultGroup,
+ generateDefaultGroupByProperty,
getCardGroup,
getGroupOptions,
moveCardToAfter,
@@ -48,6 +48,7 @@ export const useRecastKanbanGroupBy = () => {
// Add other type groupBy support
const supportedGroupBy = getProperties().filter(
prop =>
+ prop.type === PropertyType.Status ||
prop.type === PropertyType.Select ||
prop.type === PropertyType.MultiSelect
);
@@ -88,7 +89,8 @@ export const useRecastKanbanGroupBy = () => {
// TODO: support other property type
if (
groupByProperty.type !== PropertyType.Select &&
- groupByProperty.type !== PropertyType.MultiSelect
+ groupByProperty.type !== PropertyType.MultiSelect &&
+ groupByProperty.type !== PropertyType.Status
) {
console.warn('Not support groupBy type', groupByProperty);
@@ -134,7 +136,7 @@ export const useInitKanbanEffect = ():
}
// 3. no group by, no properties
// create a new property and set it as group by
- const prop = await createSelect(DEFAULT_GROUP_BY_PROPERTY);
+ const prop = await createSelect(generateDefaultGroupByProperty());
await setGroupBy(prop.id);
};
@@ -197,7 +199,12 @@ export const useRecastKanban = () => {
beforeBlock: string | null,
afterBlock: string | null
) => {
- await moveCardToGroup(groupBy.id, child, kanbanMap[id]);
+ await moveCardToGroup({
+ groupBy,
+ cardBlock: child,
+ group: kanbanMap[id],
+ recastBlock,
+ });
if (beforeBlock) {
const block = await editor.getBlockById(
beforeBlock
@@ -286,7 +293,12 @@ export const useKanban = () => {
);
if (isChangedGroup) {
// 1.2 Move to the target group
- await moveCardToGroup(groupBy.id, targetCard, targetGroup);
+ await moveCardToGroup({
+ groupBy,
+ cardBlock: targetCard,
+ group: targetGroup,
+ recastBlock,
+ });
}
// 2. Reorder the card
@@ -324,7 +336,12 @@ export const useKanban = () => {
}
recastBlock.append(newBlock);
const newCard = newBlock as unknown as RecastItem;
- await moveCardToGroup(groupBy.id, newCard, group);
+ await moveCardToGroup({
+ groupBy,
+ cardBlock: newCard,
+ group,
+ recastBlock,
+ });
},
[editor, groupBy.id, recastBlock]
);
diff --git a/libs/components/editor-core/src/kanban/types.ts b/libs/components/editor-core/src/kanban/types.ts
index c34371f2d8..926b274c77 100644
--- a/libs/components/editor-core/src/kanban/types.ts
+++ b/libs/components/editor-core/src/kanban/types.ts
@@ -46,7 +46,10 @@ export type DefaultGroup = KanbanGroupBase & {
type SelectGroup = KanbanGroupBase &
SelectOption & {
- type: PropertyType.Select | PropertyType.MultiSelect;
+ type:
+ | PropertyType.Select
+ | PropertyType.MultiSelect
+ | PropertyType.Status;
};
type TextGroup = KanbanGroupBase & {
diff --git a/libs/components/editor-core/src/recast-block/Context.tsx b/libs/components/editor-core/src/recast-block/Context.tsx
index 55d334039b..47ec6cfcdb 100644
--- a/libs/components/editor-core/src/recast-block/Context.tsx
+++ b/libs/components/editor-core/src/recast-block/Context.tsx
@@ -2,6 +2,7 @@ import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '../editor';
import { ComponentType, createContext, ReactNode, useContext } from 'react';
import { RecastBlock } from './types';
+import { RefPageProvider } from '../ref-page';
/**
* Determine whether the block supports RecastBlock
@@ -47,7 +48,7 @@ export const RecastBlockProvider = ({
return (
- {children}
+ {children}
);
};
@@ -60,7 +61,7 @@ export const useRecastBlock = () => {
const recastBlock = useContext(RecastBlockContext);
if (!recastBlock) {
throw new Error(
- 'Failed to find recastBlock! Please use the hook under `RecastTableProvider`.'
+ 'Failed to find recastBlock! Please use the hook under `RecastBlockProvider`.'
);
}
return recastBlock;
diff --git a/libs/components/editor-core/src/recast-block/README.md b/libs/components/editor-core/src/recast-block/README.md
index 59aed91be7..489412eab3 100644
--- a/libs/components/editor-core/src/recast-block/README.md
+++ b/libs/components/editor-core/src/recast-block/README.md
@@ -49,22 +49,3 @@ const SomeBlock = () => {
return ...
;
};
```
-
-## Scene
-
-**Notice: The scene API will refactor at next version.**
-
-```tsx
-const SomeBlock = () => {
- const { scene, setScene, setPage, setTable, setKanban } =
- useRecastBlockScene();
-
- return (
- <>
- Scene: {scene}
- list
- kanban
- >
- );
-};
-```
diff --git a/libs/components/editor-core/src/recast-block/group.ts b/libs/components/editor-core/src/recast-block/group.ts
index b8b25cce01..9879ba43df 100644
--- a/libs/components/editor-core/src/recast-block/group.ts
+++ b/libs/components/editor-core/src/recast-block/group.ts
@@ -32,7 +32,7 @@ export const mergeGroup = async (...groups: AsyncBlock[]) => {
);
}
- await mergeGroupProperties(...(groups as RecastBlock[]));
+ await mergeGroupProperties(...(groups as unknown as RecastBlock[]));
const [headGroup, ...restGroups] = groups;
// Add all children to the head group
@@ -174,7 +174,7 @@ export const splitGroup = async (
}
splitGroupProperties(
- group as RecastBlock,
+ group as unknown as RecastBlock,
newGroupBlock as unknown as RecastBlock
);
await group.after(newGroupBlock);
@@ -185,6 +185,22 @@ export const splitGroup = async (
return newGroupBlock;
};
+export const appendNewGroup = async (
+ editor: BlockEditor,
+ parentBlock: AsyncBlock,
+ active = false
+) => {
+ const newGroupBlock = await createGroupWithEmptyText(editor);
+ await parentBlock.append(newGroupBlock);
+ if (active) {
+ // Active text block
+ await editor.selectionManager.activeNodeByNodeId(
+ newGroupBlock.childrenIds[0]
+ );
+ }
+ return newGroupBlock;
+};
+
export const addNewGroup = async (
editor: BlockEditor,
previousBlock: AsyncBlock,
diff --git a/libs/components/editor-core/src/recast-block/history.ts b/libs/components/editor-core/src/recast-block/history.ts
new file mode 100644
index 0000000000..2d66b8fec1
--- /dev/null
+++ b/libs/components/editor-core/src/recast-block/history.ts
@@ -0,0 +1,84 @@
+import { RecastPropertyId } from './types';
+
+// TODO: The logic for keeping history should be supported by the network layer
+type Props = {
+ recastBlockId: string;
+ blockId: string;
+ propertyId: RecastPropertyId;
+};
+
+type HistoryStorageMap = {
+ [recastBlockId: string]: {
+ [propertyId: RecastPropertyId]: string[];
+ };
+};
+
+const LOCAL_STORAGE_NAME = 'TEMPORARY_HISTORY_DATA';
+
+const ensureLocalStorage = () => {
+ const data = localStorage.getItem(LOCAL_STORAGE_NAME);
+ if (!data) {
+ localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify({}));
+ }
+};
+const ensureHistoryAtom = (
+ data: HistoryStorageMap,
+ recastBlockId: string,
+ propertyId: RecastPropertyId
+): HistoryStorageMap => {
+ if (!data[recastBlockId]) {
+ data[recastBlockId] = {};
+ }
+ if (!data[recastBlockId][propertyId]) {
+ data[recastBlockId][propertyId] = [];
+ }
+ return data;
+};
+
+export const setHistory = ({ recastBlockId, blockId, propertyId }: Props) => {
+ ensureLocalStorage();
+ const data: HistoryStorageMap = JSON.parse(
+ localStorage.getItem(LOCAL_STORAGE_NAME) as string
+ );
+ ensureHistoryAtom(data, recastBlockId, propertyId);
+ const propertyHistory = data[recastBlockId][propertyId];
+
+ if (propertyHistory.includes(blockId)) {
+ const idIndex = propertyHistory.findIndex(id => id === blockId);
+ propertyHistory.splice(idIndex, 1);
+ }
+
+ propertyHistory.push(blockId);
+
+ localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
+};
+
+export const getHistory = ({ recastBlockId }: { recastBlockId: string }) => {
+ ensureLocalStorage();
+ const data: HistoryStorageMap = JSON.parse(
+ localStorage.getItem(LOCAL_STORAGE_NAME) as string
+ );
+
+ return data[recastBlockId] ?? {};
+};
+
+export const removeHistory = ({
+ recastBlockId,
+ blockId,
+ propertyId,
+}: Props) => {
+ ensureLocalStorage();
+ const data: HistoryStorageMap = JSON.parse(
+ localStorage.getItem(LOCAL_STORAGE_NAME) as string
+ );
+ ensureHistoryAtom(data, recastBlockId, propertyId);
+
+ const propertyHistory = data[recastBlockId][propertyId];
+
+ if (propertyHistory.includes(blockId)) {
+ const idIndex = propertyHistory.findIndex(id => id === blockId);
+ propertyHistory.splice(idIndex, 1);
+ }
+
+ localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(data));
+};
diff --git a/libs/components/editor-core/src/recast-block/property.ts b/libs/components/editor-core/src/recast-block/property.ts
index e8f0104269..3676a1777d 100644
--- a/libs/components/editor-core/src/recast-block/property.ts
+++ b/libs/components/editor-core/src/recast-block/property.ts
@@ -15,6 +15,7 @@ import {
SelectProperty,
TABLE_VALUES_KEY,
} from './types';
+import { getHistory, removeHistory, setHistory } from './history';
/**
* Generate a unique id for a property
@@ -240,7 +241,13 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
return props[id];
};
- const setValue = (newValue: RecastBlockValue) => {
+ const setValue = (newValue: RecastBlockValue, recastBlockId: string) => {
+ setHistory({
+ recastBlockId: recastBlockId,
+ blockId: block.id,
+ propertyId: newValue.id,
+ });
+
return recastItem.setProperty(TABLE_VALUES_KEY, {
...props,
[newValue.id]: newValue,
@@ -249,22 +256,30 @@ export const getRecastItemValue = (block: RecastItem | AsyncBlock) => {
const removeValue = (propertyId: RecastPropertyId) => {
const { [propertyId]: omitted, ...restProps } = props;
+
+ removeHistory({
+ recastBlockId: block.id,
+ propertyId: propertyId,
+ blockId: block.id,
+ });
+
return recastItem.setProperty(TABLE_VALUES_KEY, restProps);
};
- return { getAllValue, getValue, setValue, removeValue };
+
+ const getValueHistory = getHistory;
+
+ return { getAllValue, getValue, setValue, removeValue, getValueHistory };
};
const isSelectLikeProperty = (
metaProperty?: RecastMetaProperty
-): metaProperty is SelectProperty | MultiSelectProperty => {
- if (
- !metaProperty ||
- (metaProperty.type !== PropertyType.Select &&
- metaProperty.type !== PropertyType.MultiSelect)
- ) {
- return false;
- }
- return true;
+): metaProperty is SelectProperty | MultiSelectProperty | StatusProperty => {
+ return (
+ metaProperty &&
+ (metaProperty.type === PropertyType.Status ||
+ metaProperty.type === PropertyType.Select ||
+ metaProperty.type === PropertyType.MultiSelect)
+ );
};
/**
@@ -312,7 +327,7 @@ export const useSelectProperty = () => {
};
const updateSelect = (
- selectProperty: SelectProperty | MultiSelectProperty
+ selectProperty: StatusProperty | SelectProperty | MultiSelectProperty
) => {
// if (typeof selectProperty === 'string') {
// const maybeSelectProperty = getProperty(selectProperty);
diff --git a/libs/components/editor-core/src/recast-block/view.ts b/libs/components/editor-core/src/recast-block/view.ts
index c41d09809e..09e4644977 100644
--- a/libs/components/editor-core/src/recast-block/view.ts
+++ b/libs/components/editor-core/src/recast-block/view.ts
@@ -1,5 +1,5 @@
import { nanoid } from 'nanoid';
-import { useCallback } from 'react';
+import { MutableRefObject, useCallback, useEffect, useState } from 'react';
import { useRecastBlock } from './Context';
import {
KanbanView,
@@ -50,7 +50,33 @@ export const useCurrentView = () => {
);
return [currentView, setCurrentView] as const;
};
+export const useLazyIframe = (
+ link: string,
+ timers: number,
+ container: MutableRefObject
+) => {
+ const [iframeShow, setIframeShow] = useState(false);
+ useEffect(() => {
+ const iframe = document.createElement('iframe');
+ iframe.src = link;
+ iframe.onload = () => {
+ setTimeout(() => {
+ // Prevent iframe from scrolling parent container
+ // TODO W3C https://github.com/w3c/csswg-drafts/issues/7134
+ // https://forum.figma.com/t/prevent-figmas-embed-code-from-automatically-scrolling-to-it-on-page-load/26029/6
+ setIframeShow(true);
+ }, timers);
+ };
+ if (container?.current) {
+ container.current.appendChild(iframe);
+ }
+ return () => {
+ iframe.remove();
+ };
+ }, [link, container]);
+ return iframeShow;
+};
export const useRecastView = () => {
const recastBlock = useRecastBlock();
const recastViews =
diff --git a/libs/components/editor-core/src/ref-page/ModalPage.tsx b/libs/components/editor-core/src/ref-page/ModalPage.tsx
new file mode 100644
index 0000000000..52e1d6554f
--- /dev/null
+++ b/libs/components/editor-core/src/ref-page/ModalPage.tsx
@@ -0,0 +1,87 @@
+import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui';
+import { createContext, ReactNode, useContext, useState } from 'react';
+import { createPortal } from 'react-dom';
+import { RenderBlock } from '../render-block';
+
+const Dialog = styled('div')({
+ flex: 1,
+ width: '880px',
+ margin: '72px auto',
+ background: '#fff',
+ boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ borderRadius: '10px',
+ padding: '72px 120px',
+ overflow: 'scroll',
+});
+
+const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
+ const theme = useTheme();
+ const { closeSubPage } = useRefPage();
+
+ return createPortal(
+
+ {
+ e.stopPropagation();
+ }}
+ >
+ {children}
+
+ ,
+
+ document.body
+ );
+};
+
+const ModalPage = ({ blockId }: { blockId: string | null }) => {
+ return (
+
+ {blockId && }
+
+ );
+};
+
+const RefPageContext = createContext<
+ ReturnType> | undefined
+>(undefined);
+
+export const RefPageProvider = ({ children }: { children: ReactNode }) => {
+ const state = useState();
+ const [blockId, setBlockId] = state;
+
+ return (
+
+ {children}
+
+
+ );
+};
+
+export const useRefPage = () => {
+ const context = useContext(RefPageContext);
+ if (!context) {
+ throw new Error(
+ 'Wrap your app inside of a `SubPageProvider` to have access to the hook context!'
+ );
+ }
+ const [blockId, setBlockId] = context;
+ const openSubPage = (blockId: string) => {
+ setBlockId(blockId);
+ };
+ const closeSubPage = () => {
+ setBlockId(null);
+ };
+
+ return { blockId, open: !!blockId, openSubPage, closeSubPage };
+};
+
+// export const openSubPage = () => {};
diff --git a/libs/components/editor-core/src/ref-page/index.ts b/libs/components/editor-core/src/ref-page/index.ts
new file mode 100644
index 0000000000..4b06310a5c
--- /dev/null
+++ b/libs/components/editor-core/src/ref-page/index.ts
@@ -0,0 +1 @@
+export { useRefPage, RefPageProvider } from './ModalPage';
diff --git a/libs/components/editor-core/src/render-block/RenderBlock.tsx b/libs/components/editor-core/src/render-block/RenderBlock.tsx
index 46f068af2b..74f6c659cf 100644
--- a/libs/components/editor-core/src/render-block/RenderBlock.tsx
+++ b/libs/components/editor-core/src/render-block/RenderBlock.tsx
@@ -1,8 +1,8 @@
-import { styled, Theme } from '@toeverything/components/ui';
-import { FC, useContext, useLayoutEffect, useMemo, useRef } from 'react';
+import { styled } from '@toeverything/components/ui';
+import { FC, useLayoutEffect, useMemo, useRef } from 'react';
// import { RenderChildren } from './RenderChildren';
-import { RootContext } from '../contexts';
+import { useEditor } from '../Contexts';
import { useBlock } from '../hooks';
interface RenderBlockProps {
@@ -14,7 +14,7 @@ export const RenderBlock: FC = ({
blockId,
hasContainer = true,
}) => {
- const { editor, editorElement } = useContext(RootContext);
+ const { editor, editorElement } = useEditor();
const { block } = useBlock(blockId);
const blockRef = useRef(null);
diff --git a/libs/components/editor-plugins/src/comment/Container.tsx b/libs/components/editor-plugins/src/comment/Container.tsx
index 6c0ed17b4c..fd8568725f 100644
--- a/libs/components/editor-plugins/src/comment/Container.tsx
+++ b/libs/components/editor-plugins/src/comment/Container.tsx
@@ -64,7 +64,7 @@ const StyledContainerForAddCommentContainer = styled('div')(({ theme }) => {
zIndex: 1,
display: 'flex',
borderRadius: theme.affine.shape.borderRadius,
- boxShadow: theme.affine.shadows.shadowSxDownLg,
+ boxShadow: theme.affine.shadows.shadow1,
backgroundColor: theme.affine.palette.white,
};
});
diff --git a/libs/components/editor-plugins/src/menu/command-menu/Container.tsx b/libs/components/editor-plugins/src/menu/command-menu/Container.tsx
index 70522d9b9c..43092ea535 100644
--- a/libs/components/editor-plugins/src/menu/command-menu/Container.tsx
+++ b/libs/components/editor-plugins/src/menu/command-menu/Container.tsx
@@ -31,7 +31,7 @@ const RootContainer = styled('div')(({ theme }) => {
width: 352,
maxHeight: 525,
borderRadius: '10px',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ boxShadow: theme.affine.shadows.shadow1,
backgroundColor: '#fff',
padding: '8px 4px',
};
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 11e8d665da..78b6ef5ddd 100644
--- a/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx
+++ b/libs/components/editor-plugins/src/menu/command-menu/Menu.tsx
@@ -242,25 +242,29 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
onKeyUpCapture={handleKeyup}
ref={commandMenuContentRef}
>
-
-
-
-
-
+ {show ? (
+
+
+
+
+
+ ) : (
+ <>>
+ )}
);
};
diff --git a/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx b/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx
index a323ca1961..73ab1fde1c 100644
--- a/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx
+++ b/libs/components/editor-plugins/src/menu/group-menu/DragItem.tsx
@@ -4,7 +4,7 @@ import { HandleParentIcon } from '@toeverything/components/icons';
import { styled } from '@toeverything/components/ui';
import { Point } from '@toeverything/utils';
-export const ICON_WIDTH = 24;
+export const ICON_WIDTH = 16;
type DragItemProps = {
isShow: boolean;
@@ -30,17 +30,21 @@ export const DragItem = function ({
);
};
-const StyledDiv = styled('div')({
+const StyledDiv = styled('div')(({ theme }) => ({
padding: '0',
- display: 'inlineFlex',
+ display: 'inline-flex',
width: `${ICON_WIDTH}px`,
- height: `${ICON_WIDTH}px`,
+ height: '20px',
cursor: 'grab',
+ '& svg': {
+ fontSize: '20px',
+ marginLeft: '-2px',
+ },
':hover': {
- backgroundColor: '#edeef0',
+ backgroundColor: '#F5F7F8',
borderRadius: '4px',
},
-});
+}));
const StyledButton = styled('div')({
padding: '0',
diff --git a/libs/components/editor-plugins/src/menu/inline-menu/Container.tsx b/libs/components/editor-plugins/src/menu/inline-menu/Container.tsx
index 3959d1c60b..27807b6186 100644
--- a/libs/components/editor-plugins/src/menu/inline-menu/Container.tsx
+++ b/libs/components/editor-plugins/src/menu/inline-menu/Container.tsx
@@ -75,13 +75,13 @@ export const InlineMenuContainer = ({ editor }: InlineMenuContainerProps) => {
) : null;
};
-const ToolbarContainer = styled('div')({
+const ToolbarContainer = styled('div')(({ theme }) => ({
position: 'absolute',
zIndex: 1,
display: 'flex',
alignItems: 'center',
padding: '0 12px',
borderRadius: '10px',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ boxShadow: theme.affine.shadows.shadow1,
backgroundColor: '#fff',
-});
+}));
diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx
index 602c08aacb..6c7e357b50 100644
--- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx
+++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuDraggable.tsx
@@ -1,10 +1,19 @@
-import { useState, useEffect, FC } from 'react';
+import {
+ useState,
+ useEffect,
+ FC,
+ type MouseEvent,
+ type DragEvent,
+ type ReactNode,
+ type CSSProperties,
+} from 'react';
import {
Virgo,
BlockDomInfo,
PluginHooks,
BlockDropPlacement,
+ LINE_GAP,
} from '@toeverything/framework/virgo';
import { Button } from '@toeverything/components/common';
import { styled } from '@toeverything/components/ui';
@@ -14,7 +23,7 @@ import { distinctUntilChanged, Subject } from 'rxjs';
import { HandleChildIcon } from '@toeverything/components/icons';
import { MENU_WIDTH } from './menu-config';
-const MENU_BUTTON_OFFSET = 12;
+const MENU_BUTTON_OFFSET = 4;
export type LineInfoSubject = Subject<
| {
@@ -64,13 +73,13 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
};
const bottomLineStyle = {
...horizontalLineStyle,
- top: intersectionRect.bottom + 1 - rootRect.y,
+ top: intersectionRect.bottom + 1 - rootRect.y - LINE_GAP,
};
const verticalLineStyle = {
...lineStyle,
width: 2,
- height: intersectionRect.height,
+ height: intersectionRect.height - LINE_GAP,
top: intersectionRect.y - rootRect.y,
};
const leftLineStyle = {
@@ -93,10 +102,10 @@ function Line(props: { lineInfo: LineInfo; rootRect: DOMRect }) {
}
function DragComponent(props: {
- children: React.ReactNode;
- style: React.CSSProperties;
- onDragStart: (event: React.DragEvent) => void;
- onDragEnd: (event: React.DragEvent) => void;
+ children: ReactNode;
+ style: CSSProperties;
+ onDragStart: (event: DragEvent) => void;
+ onDragEnd: (event: DragEvent) => void;
}) {
const { style, children, onDragStart, onDragEnd } = props;
return (
@@ -122,7 +131,7 @@ export const LeftMenuDraggable: FC = props => {
const [block, setBlock] = useState();
const [line, setLine] = useState(undefined);
- const handleDragStart = async (event: React.DragEvent) => {
+ const handleDragStart = async (event: DragEvent) => {
event.stopPropagation();
setVisible(false);
@@ -138,12 +147,12 @@ export const LeftMenuDraggable: FC = props => {
}
};
- const handleDragEnd = (event: React.DragEvent) => {
+ const handleDragEnd = (event: DragEvent) => {
event.preventDefault();
setLine(undefined);
};
- const onClick = (event: React.MouseEvent) => {
+ const onClick = (event: MouseEvent) => {
if (block == null) return;
const currentTarget = event.currentTarget;
editor.selection.setSelectedNodesIds([block.blockId]);
@@ -200,11 +209,10 @@ export const LeftMenuDraggable: FC = props => {
style={{
position: 'absolute',
left:
- Math.min(
- block.rect.left -
- MENU_WIDTH -
- MENU_BUTTON_OFFSET
- ) - rootRect.left,
+ block.rect.left -
+ MENU_WIDTH -
+ MENU_BUTTON_OFFSET -
+ rootRect.left,
top: block.rect.top - rootRect.top,
opacity: visible ? 1 : 0,
zIndex: 1,
@@ -239,15 +247,19 @@ const Draggable = styled(Button)({
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'transparent',
- width: '24px',
- height: '24px',
+ width: '16px',
+ height: '22px',
+ '& svg': {
+ fontSize: '20px',
+ marginLeft: '-2px',
+ },
':hover': {
- backgroundColor: '#edeef0',
+ backgroundColor: '#F5F7F8',
borderRadius: '4px',
},
});
const LigoLeftMenu = styled('div')({
backgroundColor: 'transparent',
- marginRight: '4px',
+ // marginRight: '4px',
});
diff --git a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx
index 00d301287e..806c93c971 100644
--- a/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx
+++ b/libs/components/editor-plugins/src/menu/left-menu/LeftMenuPlugin.tsx
@@ -1,5 +1,5 @@
import { BlockDomInfo, HookType } from '@toeverything/framework/virgo';
-import React, { StrictMode } from 'react';
+import { StrictMode } from 'react';
import { BasePlugin } from '../../base-plugin';
import { ignoreBlockTypes } from './menu-config';
import { LineInfoSubject, LeftMenuDraggable } from './LeftMenuDraggable';
@@ -37,7 +37,7 @@ export class LeftMenuPlugin extends BasePlugin {
);
this.sub.add(
this.hooks
- .get(HookType.AFTER_ON_NODE_DRAG_OVER)
+ .get(HookType.ON_ROOTNODE_DRAG_OVER)
.subscribe(this._handleDragOverBlockNode)
);
this.sub.add(
@@ -65,24 +65,30 @@ export class LeftMenuPlugin extends BasePlugin {
private _onDrop = () => {
this._lineInfo.next(undefined);
};
- private _handleDragOverBlockNode = async ([event, blockInfo]: [
- React.DragEvent,
- BlockDomInfo
- ]) => {
- const { type, dom, blockId } = blockInfo;
+ private _handleDragOverBlockNode = async (
+ event: React.DragEvent
+ ) => {
event.preventDefault();
- if (this.editor.dragDropManager.isDragBlock(event)) {
- if (ignoreBlockTypes.includes(type)) {
- return;
- }
- const direction =
- await this.editor.dragDropManager.checkBlockDragTypes(
- event,
- dom,
- blockId
- );
- this._lineInfo.next({ direction, blockInfo });
- }
+ if (!this.editor.dragDropManager.isDragBlock(event)) return;
+ const block = await this.editor.getBlockByPoint(
+ new Point(event.clientX, event.clientY)
+ );
+ if (block == null || ignoreBlockTypes.includes(block.type)) return;
+ const direction = await this.editor.dragDropManager.checkBlockDragTypes(
+ event,
+ block.dom,
+ block.id
+ );
+ this._lineInfo.next({
+ direction,
+ blockInfo: {
+ blockId: block.id,
+ dom: block.dom,
+ rect: block.dom.getBoundingClientRect(),
+ type: block.type,
+ properties: block.getProperties(),
+ },
+ });
};
private _handleMouseMove = async (
diff --git a/libs/components/editor-plugins/src/menu/reference-menu/Container.tsx b/libs/components/editor-plugins/src/menu/reference-menu/Container.tsx
index 07c355e377..47a1672d4d 100644
--- a/libs/components/editor-plugins/src/menu/reference-menu/Container.tsx
+++ b/libs/components/editor-plugins/src/menu/reference-menu/Container.tsx
@@ -8,6 +8,7 @@ import {
commonListContainer,
} from '@toeverything/components/common';
import { domToRect } from '@toeverything/utils';
+import { styled } from '@toeverything/components/ui';
import { QueryResult } from '../../search';
@@ -152,13 +153,12 @@ export const ReferenceMenuContainer = ({
}, [hooks, handle_key_down]);
return isShow ? (
-
+
+
) : null;
};
-const styles = style9.create({
- rootContainer: {
- position: 'fixed',
- zIndex: 1,
- maxHeight: 525,
- borderRadius: '10px',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
- backgroundColor: '#fff',
- padding: '8px 4px',
- },
- contentContainer: {
- display: 'flex',
- overflow: 'hidden',
- maxHeight: 493,
- },
-});
+const RootContainer = styled('div')(({ theme }) => ({
+ position: 'fixed',
+ zIndex: 1,
+ maxHeight: '525px',
+ borderRadius: '10px',
+ boxShadow: theme.affine.shadows.shadow1,
+ backgroundColor: '#fff',
+ padding: '8px 4px',
+}));
+
+const ContentContainer = styled('div')(({ theme }) => ({
+ display: 'flex',
+ overflow: 'hidden',
+ maxHeight: '493px',
+}));
diff --git a/libs/components/editor-plugins/src/placeholder/PlaceholderPanel.tsx b/libs/components/editor-plugins/src/placeholder/PlaceholderPanel.tsx
index 429336c976..89b66256f7 100644
--- a/libs/components/editor-plugins/src/placeholder/PlaceholderPanel.tsx
+++ b/libs/components/editor-plugins/src/placeholder/PlaceholderPanel.tsx
@@ -141,8 +141,7 @@ export const PlaceholderPanel = (props: PlaceholderPanelProps) => {
setOpen(false);
props.onClickTips();
};
- const templateList: Array =
- TemplateFactory.defaultTemplateList;
+ const templateList = TemplateFactory.defaultTemplateList;
const handleNewFromTemplate = async (template: TemplateMeta) => {
const pageId = await editor.getRootBlockId();
const newPage = await services.api.editorBlock.getBlock(
@@ -162,33 +161,30 @@ export const PlaceholderPanel = (props: PlaceholderPanelProps) => {
setOpen(false);
};
return (
- <>
-
-
- Press Enter to continue with an empty page, or pick a
- template
-
-
- {templateList.map((template, index) => {
- return (
- {
- handleNewFromTemplate(template);
- }}
- >
- {template.name}
-
- );
- })}
-
-
- >
+
+
+ Press Enter to continue with an empty page, or pick a template
+
+
+ {templateList.map((template, index) => {
+ return (
+ {
+ handleNewFromTemplate(template);
+ }}
+ >
+ {template.name}
+
+ );
+ })}
+
+
);
};
diff --git a/libs/components/editor-plugins/src/search/Search.tsx b/libs/components/editor-plugins/src/search/Search.tsx
index 5ab54b32ff..6b02b6334e 100644
--- a/libs/components/editor-plugins/src/search/Search.tsx
+++ b/libs/components/editor-plugins/src/search/Search.tsx
@@ -7,6 +7,7 @@ import {
TransitionsModal,
MuiBox as Box,
MuiBox,
+ styled,
} from '@toeverything/components/ui';
import { Virgo, BlockEditor } from '@toeverything/framework/virgo';
import { throttle } from '@toeverything/utils';
@@ -21,26 +22,6 @@ const styles = style9.create({
display: 'flex',
flexDirection: 'column',
},
- search: {
- margin: '0.5em',
- backgroundColor: 'white',
- boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)',
- padding: '16px 32px',
- borderRadius: '10px',
- },
- result: {
- margin: '0.5em',
- backgroundColor: 'white',
- boxShadow: '0px 1px 10px rgb(152 172 189 / 60%)',
- padding: '16px 32px',
- borderRadius: '10px',
- transitionProperty: 'max-height',
- transitionDuration: '300ms',
- transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
- transitionDelay: '0ms',
- overflowX: 'hidden',
- overflowY: 'hidden',
- },
resultItem: {
width: '100%',
},
@@ -96,15 +77,14 @@ export const Search = (props: SearchProps) => {
}}
>
- set_search(e.target.value)}
/>
-
@@ -119,8 +99,30 @@ export const Search = (props: SearchProps) => {
}}
/>
))}
-
+
);
};
+
+const SearchInput = styled('input')(({ theme }) => ({
+ margin: '0.5em',
+ backgroundColor: 'white',
+ boxShadow: theme.affine.shadows.shadow1,
+ padding: '16px 32px',
+ borderRadius: '10px',
+}));
+
+const ResultContainer = styled(MuiBox)(({ theme }) => ({
+ margin: '0.5em',
+ backgroundColor: 'white',
+ boxShadow: theme.affine.shadows.shadow1,
+ padding: '16px 32px',
+ borderRadius: '10px',
+ transitionProperty: 'max-height',
+ transitionDuration: '300ms',
+ transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ transitionDelay: '0ms',
+ overflowX: 'hidden',
+ overflowY: 'hidden',
+}));
diff --git a/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx b/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx
index ada3a92f57..a30e17f913 100644
--- a/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx
+++ b/libs/components/layout/src/header/EditorBoardSwitcher/StatusIcon.tsx
@@ -20,7 +20,7 @@ const IconWrapper = styled('div')>(
width: '20px',
height: '20px',
borderRadius: '5px',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ boxShadow: theme.affine.shadows.shadow1,
color: theme.affine.palette.primary,
cursor: 'pointer',
backgroundColor: theme.affine.palette.white,
diff --git a/libs/components/layout/src/settings-sidebar/Comments/CommentItem.tsx b/libs/components/layout/src/settings-sidebar/Comments/CommentItem.tsx
index 8ebc4baf24..2ac99e7185 100644
--- a/libs/components/layout/src/settings-sidebar/Comments/CommentItem.tsx
+++ b/libs/components/layout/src/settings-sidebar/Comments/CommentItem.tsx
@@ -94,7 +94,7 @@ const StyledContainerForCommentItem = styled('div', {
transition: 'left 150ms ease-in-out',
backgroundColor: theme.affine.palette.white,
'&:hover': {
- boxShadow: theme.affine.shadows.shadowSxDownLg,
+ boxShadow: theme.affine.shadows.shadow1,
},
};
});
diff --git a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts
index 250511822d..1ce5bcb55a 100644
--- a/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts
+++ b/libs/components/layout/src/settings-sidebar/Settings/use-settings.ts
@@ -9,7 +9,7 @@ import {
importWorkspace,
exportWorkspace,
useWorkspaceAndPageId,
- useReadingMode,
+ // useReadingMode,
clearWorkspace,
} from './util';
@@ -63,20 +63,20 @@ export const useSettings = (): SettingItem[] => {
const { workspaceId, pageId } = useWorkspaceAndPageId();
const navigate = useNavigate();
const settingFlags = useSettingFlags();
- const { toggleReadingMode, readingMode } = useReadingMode();
+ // const { toggleReadingMode, readingMode } = useReadingMode();
const settings: SettingItem[] = [
- {
- type: 'switch',
- name: 'Reading Mode',
- value: readingMode,
- onChange: () => {
- toggleReadingMode();
- },
- },
- {
- type: 'separator',
- },
+ // {
+ // type: 'switch',
+ // name: 'Reading Mode',
+ // value: readingMode,
+ // onChange: () => {
+ // toggleReadingMode();
+ // },
+ // },
+ // {
+ // type: 'separator',
+ // },
{
type: 'button',
name: 'Duplicate Page',
diff --git a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx
index b0527c5e42..a532d9bd02 100644
--- a/libs/components/layout/src/workspace-sidebar/activities/activities.tsx
+++ b/libs/components/layout/src/workspace-sidebar/activities/activities.tsx
@@ -12,17 +12,22 @@ import { useNavigate } from 'react-router';
import { formatDistanceToNow } from 'date-fns';
const StyledWrapper = styled('div')({
- margin: '0 16px 0 32px',
+ paddingLeft: '12px',
span: {
textOverflow: 'ellipsis',
overflow: 'hidden',
},
'.item': {
+ height: '32px',
display: 'flex',
alignItems: 'center',
- ustifyContent: 'space-between',
- padding: '7px 0px',
+ justifyContent: 'space-between',
+ paddingRight: '20px',
whiteSpace: 'nowrap',
+ '&:hover': {
+ background: '#f5f7f8',
+ borderRadius: '5px',
+ },
},
'.itemButton': {
padding: 0,
@@ -31,6 +36,7 @@ const StyledWrapper = styled('div')({
'.itemLeft': {
color: '#4c6275',
marginRight: '20px',
+ cursor: 'pointer',
span: {
fontSize: 14,
},
@@ -44,34 +50,39 @@ const StyledWrapper = styled('div')({
},
});
+const StyledItemContent = styled('div')({
+ width: '100%',
+ height: '32px',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+});
+
export const Activities = () => {
const navigate = useNavigate();
const { user, currentSpaceId } = useUserAndSpaces();
const [recentPages, setRecentPages] = useState([]);
const userId = user?.id;
- /* 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]);
-
/* show recently edit documents */
- const getRecentEditPages = async (state, block) => {
- console.log(state, await block.children());
- };
+ const getRecentEditPages = useCallback(async () => {
+ if (!userId || !currentSpaceId) {
+ return;
+ }
+
+ const recentEditPages =
+ (await services.api.userConfig.getRecentEditedPages(
+ currentSpaceId
+ )) || [];
+
+ setRecentPages(recentEditPages);
+ }, [currentSpaceId, userId]);
+
+ useEffect(() => {
+ (async () => {
+ await getRecentEditPages();
+ })();
+ }, [getRecentEditPages]);
useEffect(() => {
let unobserve: () => void;
@@ -90,12 +101,12 @@ export const Activities = () => {
return (
-
- {recentPages.map(({ id, title, lastOpenTime }) => {
+
+ {recentPages.map(item => {
+ const { id, title, updated } = item;
return (
- {
navigate(`/${currentSpaceId}/${id}`);
}}
@@ -106,11 +117,11 @@ export const Activities = () => {
/>
-
+
);
})}
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 0c58d60fb0..57d7643bba 100755
--- a/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx
+++ b/libs/components/layout/src/workspace-sidebar/page-tree/DndTree.tsx
@@ -44,7 +44,7 @@ export type DndTreeProps = {
*/
export function DndTree(props: DndTreeProps) {
const {
- indentationWidth = 16,
+ indentationWidth = 12,
collapsible,
removable,
showDragIndicator,
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 a524dff76e..96c14522fc 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
@@ -101,9 +101,6 @@ export const TreeItem = forwardRef(
))}
- {/**/}
- {/* */}
- {/* */}
* {
@@ -69,7 +72,6 @@
box-sizing: border-box;
display: flex;
align-items: center;
- background-color: #fff;
color: #4c6275;
}
@@ -81,7 +83,6 @@
display: flex;
align-items: center;
justify-content: space-around;
- background-color: #fff;
color: #4c6275;
padding-right: 0.5rem;
overflow: hidden;
@@ -96,11 +97,6 @@
display: block;
}
}
-
- &:hover {
- background: #f5f7f8;
- border-radius: 5px;
- }
}
.Text {
@@ -167,14 +163,6 @@
background-color: transparent;
-webkit-tap-highlight-color: transparent;
- &:hover {
- background-color: var(--action-background, rgba(0, 0, 0, 0.05));
-
- svg {
- fill: #6f7b88;
- }
- }
-
svg {
flex: 0 0 auto;
margin: auto;
diff --git a/libs/components/ui/src/cascader/Cascader.tsx b/libs/components/ui/src/cascader/Cascader.tsx
index 5ff6fe608d..ef486e5029 100644
--- a/libs/components/ui/src/cascader/Cascader.tsx
+++ b/libs/components/ui/src/cascader/Cascader.tsx
@@ -133,7 +133,7 @@ export function Cascader(props: CascaderProps) {
const MenuPaper = styled('div')(({ theme }) => ({
fontFamily: 'PingFang SC',
background: '#FFF',
- boxShadow: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ boxShadow: theme.affine.shadows.shadow1,
borderRadius: '10px 0px 10px 10px',
color: '#4C6275',
fontWeight: '400',
diff --git a/libs/components/ui/src/mui.ts b/libs/components/ui/src/mui.ts
index 94025c5f5b..47588ce5e6 100644
--- a/libs/components/ui/src/mui.ts
+++ b/libs/components/ui/src/mui.ts
@@ -13,6 +13,7 @@ import type {
} from '@mui/material';
import {
Avatar,
+ Backdrop,
Box,
Button,
Checkbox,
@@ -51,6 +52,7 @@ import {
tooltipClasses,
Typography,
Zoom,
+ Fade,
} from '@mui/material';
export { alpha } from '@mui/system';
@@ -233,7 +235,16 @@ export const MuiInput = Input;
*/
export const MuiZoom = Zoom;
+/**
+ * @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
+ */
+export const MuiFade = Fade;
+
/**
* @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
*/
export const MuiRadio = Radio;
+/**
+ * @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
+ */
+export const MuiBackdrop = Backdrop;
diff --git a/libs/components/ui/src/popover/container.tsx b/libs/components/ui/src/popover/Container.tsx
similarity index 85%
rename from libs/components/ui/src/popover/container.tsx
rename to libs/components/ui/src/popover/Container.tsx
index f91e95b024..3f7f8c382f 100644
--- a/libs/components/ui/src/popover/container.tsx
+++ b/libs/components/ui/src/popover/Container.tsx
@@ -12,14 +12,14 @@ const border_radius_map: Record = {
export const PopoverContainer = styled('div')<
Pick
>(({ theme, direction, style }) => {
- const shadow = theme.affine.shadows.shadowSxDownLg;
+ const shadow = theme.affine.shadows.shadow1;
const white = theme.affine.palette.white;
- const border_radius =
+ const borderRadius =
border_radius_map[direction] || border_radius_map['left-top'];
return {
boxShadow: shadow,
- borderRadius: border_radius,
+ borderRadius: borderRadius,
padding: '8px 4px',
backgroundColor: white,
...style,
diff --git a/libs/components/ui/src/popover/Popover.tsx b/libs/components/ui/src/popover/Popover.tsx
index 67ab3f3c29..d03a65f322 100644
--- a/libs/components/ui/src/popover/Popover.tsx
+++ b/libs/components/ui/src/popover/Popover.tsx
@@ -1,7 +1,7 @@
import type { MuiPopperPlacementType as PopperPlacementType } from '../mui';
import React, { forwardRef, type PropsWithChildren } from 'react';
import { type PopperHandler, Popper } from '../popper';
-import { PopoverContainer } from './container';
+import { PopoverContainer } from './Container';
import type { PopoverProps, PopoverDirection } from './interface';
export const placementToContainerDirection: Record<
diff --git a/libs/components/ui/src/popover/index.tsx b/libs/components/ui/src/popover/index.ts
similarity index 53%
rename from libs/components/ui/src/popover/index.tsx
rename to libs/components/ui/src/popover/index.ts
index c829b413b6..718c074cce 100644
--- a/libs/components/ui/src/popover/index.tsx
+++ b/libs/components/ui/src/popover/index.ts
@@ -1,3 +1,3 @@
export * from './Popover';
export * from './interface';
-export { PopoverContainer } from './container';
+export { PopoverContainer } from './Container';
diff --git a/libs/components/ui/src/select/Select.tsx b/libs/components/ui/src/select/Select.tsx
index ebbf585e11..d1f3141e69 100644
--- a/libs/components/ui/src/select/Select.tsx
+++ b/libs/components/ui/src/select/Select.tsx
@@ -117,7 +117,7 @@ const StyledListbox = styled('ul')(({ theme }) => ({
background: '#fff',
borderRadius: '10px',
overflow: 'auto',
- boxShadow: theme.affine.shadows.shadowSxDownLg,
+ boxShadow: theme.affine.shadows.shadow1,
}));
const StyledPopper = styled(PopperUnstyled)`
diff --git a/libs/components/ui/src/theme/theme.ts b/libs/components/ui/src/theme/theme.ts
index a51bca8827..70aace8122 100644
--- a/libs/components/ui/src/theme/theme.ts
+++ b/libs/components/ui/src/theme/theme.ts
@@ -70,7 +70,7 @@ interface Typography {
interface Shadows {
none: 'none';
- shadowSxDownLg: string;
+ shadow1: string;
}
type StringWithNone = [
@@ -225,7 +225,7 @@ export const Theme = {
},
shadows: {
none: 'none',
- shadowSxDownLg: '0px 1px 10px rgba(152, 172, 189, 0.6)',
+ shadow1: '0px 1px 5px rgba(152, 172, 189, 0.2)',
},
border: ['none'],
spacing: {
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 953cf0e6de..88a8d08bbe 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
@@ -13,7 +13,7 @@ const groupTemplateMap = {
grid: gridTemplate,
} as GroupTemplateMap;
-const defaultTemplateList = [
+const defaultTemplateList: Array = [
{
name: 'New From Quick Start',
groupKeys: ['todolist'],
@@ -22,10 +22,10 @@ const defaultTemplateList = [
{ name: 'New From Blog', groupKeys: ['blog'] },
{ name: ' New Todolist', groupKeys: ['todolist'] },
{ name: ' New Empty Page', groupKeys: ['empty'] },
-] as const;
+];
const TemplateFactory = {
- defaultTemplateList: defaultTemplateList,
+ defaultTemplateList,
generatePageTemplateByGroupKeys(props: TemplateMeta): Template {
const newTitle = props.name || 'Get Started with AFFiNE';
const keys: GroupTemplateKeys[] = props.groupKeys || [];
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 0a5dca7eef..887bb1ec2a 100644
--- a/libs/datasource/db-service/src/services/workspace/user-config.ts
+++ b/libs/datasource/db-service/src/services/workspace/user-config.ts
@@ -3,43 +3,44 @@ import { ServiceBaseClass } from '../base';
import { ObserveCallback, ReturnUnobserve } from '../database';
import { PageTree } from './page-tree';
import { PageConfigItem } from './types';
+import type { QueryIndexMetadata } from '@toeverything/datasource/jwt';
/** Operate the user configuration at the workspace level */
export class UserConfig extends ServiceBaseClass {
- private async fetch_recent_pages(
+ private async _fetchRecentPages(
workspace: string
): Promise>> {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
- const recent_work_pages =
- workspace_db_block.getDecoration<
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
+ const recentWorkPages =
+ workspaceDbBlock.getDecoration<
Record>
>(RECENT_PAGES) || {};
- return recent_work_pages;
+ return recentWorkPages;
}
- private async save_recent_pages(
+ private async _saveRecentPages(
workspace: string,
recentPages: Record>
) {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
- workspace_db_block.setDecoration(RECENT_PAGES, recentPages);
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
+ workspaceDbBlock.setDecoration(RECENT_PAGES, recentPages);
}
async getUserInitialPage(
workspace: string,
userId: string
): Promise {
- const recent_pages = await this.getRecentPages(workspace, userId);
- if (recent_pages.length > 0) {
- return recent_pages[0].id;
+ const recentPages = await this.getRecentPages(workspace, userId);
+ if (recentPages.length > 0) {
+ return recentPages[0].id;
}
const db = await this.database.getDatabase(workspace);
- const new_page = await db.get('page');
+ const newPage = await db.get('page');
- await this.get_dependency(PageTree).addPage(workspace, new_page.id);
- await this.addRecentPage(workspace, userId, new_page.id);
- return new_page.id;
+ await this.get_dependency(PageTree).addPage(workspace, newPage.id);
+ await this.addRecentPage(workspace, userId, newPage.id);
+ return newPage.id;
}
async getRecentPages(
@@ -47,13 +48,10 @@ export class UserConfig extends ServiceBaseClass {
userId: string,
topNumber = 5
): Promise {
- const recent_work_pages = await this.fetch_recent_pages(workspace);
- const recent_pages = (recent_work_pages[userId] || []).slice(
- 0,
- topNumber
- );
+ const recentWorkPages = await this._fetchRecentPages(workspace);
+ const recentPages = (recentWorkPages[userId] || []).slice(0, topNumber);
const db = await this.database.getDatabase(workspace);
- for (const item of recent_pages) {
+ for (const item of recentPages) {
const page = await db.get(item.id as 'page');
item.title =
page
@@ -61,39 +59,39 @@ export class UserConfig extends ServiceBaseClass {
?.value?.map(v => v.text)
.join('') || 'Untitled';
}
- return recent_pages;
+ return recentPages;
}
async addRecentPage(workspace: string, userId: string, pageId: string) {
- const recent_work_pages = await this.fetch_recent_pages(workspace);
- let recent_pages = recent_work_pages[userId] || [];
- recent_pages = recent_pages.filter(item => item.id !== pageId);
- recent_pages.unshift({
+ const recentWorkPages = await this._fetchRecentPages(workspace);
+ let recentPages = recentWorkPages[userId] || [];
+ recentPages = recentPages.filter(item => item.id !== pageId);
+ recentPages.unshift({
id: pageId,
lastOpenTime: Date.now(),
});
- recent_work_pages[userId] = recent_pages;
- await this.save_recent_pages(workspace, recent_work_pages);
+ recentWorkPages[userId] = recentPages;
+ await this._saveRecentPages(workspace, recentWorkPages);
}
async removePage(workspace: string, pageId: string) {
- const recent_work_pages = await this.fetch_recent_pages(workspace);
- for (const key in recent_work_pages) {
- recent_work_pages[key] = recent_work_pages[key].filter(
+ const recentWorkPages = await this._fetchRecentPages(workspace);
+ for (const key in recentWorkPages) {
+ recentWorkPages[key] = recentWorkPages[key].filter(
item => item.id !== pageId
);
}
- await this.save_recent_pages(workspace, recent_work_pages);
+ await this._saveRecentPages(workspace, recentWorkPages);
}
async observe(
{ workspace }: { workspace: string },
callback: ObserveCallback
): Promise {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
const unobserveWorkspace = await this._observe(
workspace,
- workspace_db_block.id,
+ workspaceDbBlock.id,
(states, block) => {
callback(states, block);
}
@@ -105,24 +103,40 @@ export class UserConfig extends ServiceBaseClass {
}
async unobserve({ workspace }: { workspace: string }) {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
- await this._unobserve(workspace, workspace_db_block.id);
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
+ await this._unobserve(workspace, workspaceDbBlock.id);
}
async getWorkspaceName(workspace: string): Promise {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
const workspaceName =
- workspace_db_block.getDecoration(WORKSPACE_CONFIG) || '';
+ workspaceDbBlock.getDecoration(WORKSPACE_CONFIG) || '';
return workspaceName;
}
async getWorkspaceId(workspace: string): Promise {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
- return workspace_db_block.id;
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
+ return workspaceDbBlock.id;
}
async setWorkspaceName(workspace: string, workspaceName: string) {
- const workspace_db_block = await this.getWorkspaceDbBlock(workspace);
- workspace_db_block.setDecoration(WORKSPACE_CONFIG, workspaceName);
+ const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
+ workspaceDbBlock.setDecoration(WORKSPACE_CONFIG, workspaceName);
+ }
+
+ async getRecentEditedPages(workspace: string) {
+ const db = await this.database.getDatabase(workspace);
+ const recentEditedPages =
+ (await db.queryBlocks({
+ $sort: 'lastUpdated',
+ $desc: false /* sort rule: true(default)(ASC), or false(DESC) */,
+ $limit: 4,
+ flavor: 'page',
+ } as QueryIndexMetadata)) || [];
+
+ return recentEditedPages.map(item => {
+ item['title'] = item.content || 'Untitled';
+ return item;
+ });
}
}
diff --git a/libs/datasource/feature-flags/README.md b/libs/datasource/feature-flags/README.md
index f5c59f4e13..7a04209c5d 100644
--- a/libs/datasource/feature-flags/README.md
+++ b/libs/datasource/feature-flags/README.md
@@ -2,7 +2,15 @@
## Usage
-- set provider
+- Set token at environment variable
+ - The key can be obtained from the [Feature Flag Portal](https://portal.featureflag.co/account-settings/projects)
+
+```shell
+# .env.local
+AFFINE_FEATURE_FLAG_TOKEN=XXXXXXX
+```
+
+- Set provider
```tsx
import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags';
@@ -42,7 +50,8 @@ const App = () => {
**When entering development mode feature flag will NOT be updated in real time**
-- `activateFfcDevMode()` play with feature flags locally
+- `activateFfcDevMode(PASSWORD)` play with feature flags locally
+ - The `devModePassword` can be obtained from `src/config.ts`
- `quitFfcDevMode()` quit dev mode
## Running unit tests
diff --git a/libs/datasource/feature-flags/src/config.ts b/libs/datasource/feature-flags/src/config.ts
index 7f3360d6db..5295dae995 100644
--- a/libs/datasource/feature-flags/src/config.ts
+++ b/libs/datasource/feature-flags/src/config.ts
@@ -8,4 +8,18 @@ export const config: IOption = {
// id: 'the user's unique identifier'
// }
devModePassword: '-',
+ enableDataSync: !!process.env['AFFINE_FEATURE_FLAG_TOKEN'],
+ // bootstrap: [
+ // {
+ // // the feature flag key
+ // id: 'flag',
+ // // the feature flag value
+ // variation: false,
+ // // the variation data type, string is used if not provided
+ // variationType: VariationDataType.boolean,
+ // variationOptions: [],
+ // timestamp: 0,
+ // sendToExperiment: false,
+ // },
+ // ],
};
diff --git a/libs/datasource/jwt/package.json b/libs/datasource/jwt/package.json
index 1788d382ea..4e62ef229d 100644
--- a/libs/datasource/jwt/package.json
+++ b/libs/datasource/jwt/package.json
@@ -20,6 +20,7 @@
"@types/flexsearch": "^0.7.3",
"buffer": "^6.0.3",
"debug": "^4.3.4",
+ "fast-sort": "^3.2.0",
"fflate": "^0.7.3",
"idb-keyval": "^6.2.0",
"immer": "^9.0.15",
diff --git a/libs/datasource/jwt/src/adapter/yjs/binary.ts b/libs/datasource/jwt/src/adapter/yjs/binary.ts
index d475cd05f2..468710ac4a 100644
--- a/libs/datasource/jwt/src/adapter/yjs/binary.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/binary.ts
@@ -3,29 +3,29 @@ import { Array as YArray, Map as YMap } from 'yjs';
import { RemoteKvService } from '@toeverything/datasource/remote-kv';
export class YjsRemoteBinaries {
- readonly #binaries: YMap>; // binary instance
- readonly #remote_storage?: RemoteKvService;
+ private readonly _binaries: YMap>; // binary instance
+ private readonly _remoteStorage?: RemoteKvService;
constructor(binaries: YMap>, remote_token?: string) {
- this.#binaries = binaries;
+ this._binaries = binaries;
if (remote_token) {
- this.#remote_storage = new RemoteKvService(remote_token);
+ this._remoteStorage = new RemoteKvService(remote_token);
} else {
console.warn(`Remote storage is not ready`);
}
}
has(name: string): boolean {
- return this.#binaries.has(name);
+ return this._binaries.has(name);
}
async get(name: string): Promise | undefined> {
- if (this.#binaries.has(name)) {
- return this.#binaries.get(name);
+ if (this._binaries.has(name)) {
+ return this._binaries.get(name);
} else {
// TODO: Remote Load
try {
- const file = await this.#remote_storage?.instance.getBuffData(
+ const file = await this._remoteStorage?.instance.getBuffData(
name
);
console.log(file);
@@ -38,16 +38,16 @@ export class YjsRemoteBinaries {
}
async set(name: string, binary: YArray) {
- if (!this.#binaries.has(name)) {
+ if (!this._binaries.has(name)) {
console.log(name, 'name');
if (binary.length === 1) {
- this.#binaries.set(name, binary);
- if (this.#remote_storage) {
+ this._binaries.set(name, binary);
+ if (this._remoteStorage) {
// TODO: Remote Save, if there is an object with the same name remotely, the upload is skipped, because the file name is the hash of the file content
- const has_file = this.#remote_storage.instance.exist(name);
+ const has_file = this._remoteStorage.instance.exist(name);
if (!has_file) {
const upload_file = new File(binary.toArray(), name);
- await this.#remote_storage.instance
+ await this._remoteStorage.instance
.upload(upload_file)
.catch(err => {
throw new Error(`${err} upload error`);
diff --git a/libs/datasource/jwt/src/adapter/yjs/block.ts b/libs/datasource/jwt/src/adapter/yjs/block.ts
index 2faf3b80fe..1f49ad0ed1 100644
--- a/libs/datasource/jwt/src/adapter/yjs/block.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/block.ts
@@ -32,49 +32,51 @@ type YjsBlockInstanceProps = {
};
export class YjsBlockInstance implements BlockInstance {
- readonly #id: string;
- readonly #block: YMap;
- readonly #binary?: YArray;
- readonly #children: YArray;
- readonly #set_block: (
+ private readonly _id: string;
+ private readonly _block: YMap;
+ private readonly _binary?: YArray;
+ private readonly _children: YArray;
+ private readonly _setBlock: (
id: string,
block: BlockItem
) => Promise;
- readonly #get_updated: (id: string) => number | undefined;
- readonly #get_creator: (id: string) => string | undefined;
- readonly #get_block_instance: (id: string) => YjsBlockInstance | undefined;
- readonly #children_listeners: Map;
- readonly #content_listeners: Map;
+ private readonly _getUpdated: (id: string) => number | undefined;
+ private readonly _getCreator: (id: string) => string | undefined;
+ private readonly _getBlockInstance: (
+ id: string
+ ) => YjsBlockInstance | undefined;
+ private readonly _childrenListeners: Map;
+ private readonly _contentListeners: Map;
// eslint-disable-next-line @typescript-eslint/naming-convention
- #children_map: Map;
+ _childrenMap: Map;
constructor(props: YjsBlockInstanceProps) {
- this.#id = props.id;
- this.#block = props.block;
- this.#binary = props.binary;
+ this._id = props.id;
+ this._block = props.block;
+ this._binary = props.binary;
- this.#children = props.block.get('children') as YArray;
- this.#children_map = getMapFromYArray(this.#children);
- this.#set_block = props.setBlock;
- this.#get_updated = props.getUpdated;
- this.#get_creator = props.getCreator;
- this.#get_block_instance = props.getBlockInstance;
+ this._children = props.block.get('children') as YArray;
+ this._childrenMap = getMapFromYArray(this._children);
+ this._setBlock = props.setBlock;
+ this._getUpdated = props.getUpdated;
+ this._getCreator = props.getCreator;
+ this._getBlockInstance = props.getBlockInstance;
- this.#children_listeners = new Map();
- this.#content_listeners = new Map();
+ this._childrenListeners = new Map();
+ this._contentListeners = new Map();
- const content = this.#block.get('content') as YMap;
+ const content = this._block.get('content') as YMap;
- this.#children.observe(event =>
- ChildrenListenerHandler(this.#children_listeners, event)
+ this._children.observe(event =>
+ ChildrenListenerHandler(this._childrenListeners, event)
);
content?.observeDeep(events =>
- ContentListenerHandler(this.#content_listeners, events)
+ ContentListenerHandler(this._contentListeners, events)
);
// TODO: flavor needs optimization
- this.#block.observeDeep(events =>
- ContentListenerHandler(this.#content_listeners, events)
+ this._block.observeDeep(events =>
+ ContentListenerHandler(this._contentListeners, events)
);
}
@@ -99,85 +101,85 @@ export class YjsBlockInstance implements BlockInstance {
}
addChildrenListener(name: string, listener: BlockListener): void {
- this.#children_listeners.set(name, listener);
+ this._childrenListeners.set(name, listener);
}
removeChildrenListener(name: string): void {
- this.#children_listeners.delete(name);
+ this._childrenListeners.delete(name);
}
addContentListener(name: string, listener: BlockListener): void {
- this.#content_listeners.set(name, listener);
+ this._contentListeners.set(name, listener);
}
removeContentListener(name: string): void {
- this.#content_listeners.delete(name);
+ this._contentListeners.delete(name);
}
get id() {
- return this.#id;
+ return this._id;
}
get content(): YjsContentOperation {
if (this.type === BlockTypes.block) {
- const content = this.#block.get('content');
+ const content = this._block.get('content');
if (content instanceof YAbstractType) {
return new YjsContentOperation(content);
} else {
throw new Error(`Invalid content type: ${typeof content}`);
}
- } else if (this.type === BlockTypes.binary && this.#binary) {
- return new YjsContentOperation(this.#binary);
+ } else if (this.type === BlockTypes.binary && this._binary) {
+ return new YjsContentOperation(this._binary);
}
throw new Error(
- `Invalid content type: ${this.type}, ${this.#block.get(
+ `Invalid content type: ${this.type}, ${this._block.get(
'content'
- )}, ${this.#binary}`
+ )}, ${this._binary}`
);
}
get type(): BlockItem['type'] {
- return this.#block.get(
+ return this._block.get(
'type'
) as BlockItem['type'];
}
get flavor(): BlockItem['flavor'] {
- return this.#block.get(
+ return this._block.get(
'flavor'
) as BlockItem['flavor'];
}
// TODO: bad case. Need to optimize.
setFlavor(flavor: BlockItem['flavor']) {
- this.#block.set('flavor', flavor);
+ this._block.set('flavor', flavor);
}
get created(): BlockItem['created'] {
- return this.#block.get(
+ return this._block.get(
'created'
) as BlockItem['created'];
}
get updated(): number {
- return this.#get_updated(this.#id) || this.created;
+ return this._getUpdated(this._id) || this.created;
}
get creator(): string | undefined {
- return this.#get_creator(this.#id);
+ return this._getCreator(this._id);
}
get children(): string[] {
- return this.#children.toArray();
+ return this._children.toArray();
}
getChildren(ids?: (string | undefined)[]): YjsBlockInstance[] {
const query_ids = ids?.filter((id): id is string => !!id) || [];
- const exists_ids = this.#children.map(id => id);
+ const exists_ids = this._children.map(id => id);
const filter_ids = query_ids.length ? query_ids : exists_ids;
return exists_ids
.filter(id => filter_ids.includes(id))
- .map(id => this.#get_block_instance(id))
+ .map(id => this._getBlockInstance(id))
.filter((v): v is YjsBlockInstance => !!v);
}
@@ -196,7 +198,7 @@ export class YjsBlockInstance implements BlockInstance {
return pos;
}
} else if (before) {
- const current_pos = this.#children_map.get(before || '');
+ const current_pos = this._childrenMap.get(before || '');
if (
typeof current_pos === 'number' &&
Number.isInteger(current_pos)
@@ -207,7 +209,7 @@ export class YjsBlockInstance implements BlockInstance {
}
}
} else if (after) {
- const current_pos = this.#children_map.get(after || '');
+ const current_pos = this._childrenMap.get(after || '');
if (
typeof current_pos === 'number' &&
Number.isInteger(current_pos)
@@ -227,44 +229,44 @@ export class YjsBlockInstance implements BlockInstance {
): Promise {
const content = block[GET_BLOCK_ITEM]();
if (content) {
- const lastIndex = this.#children_map.get(block.id);
+ const lastIndex = this._childrenMap.get(block.id);
if (typeof lastIndex === 'number') {
- this.#children.delete(lastIndex);
- this.#children_map = getMapFromYArray(this.#children);
+ this._children.delete(lastIndex);
+ this._childrenMap = getMapFromYArray(this._children);
}
const position = this.position_calculator(
- this.#children_map.size,
+ this._childrenMap.size,
pos
);
if (typeof position === 'number') {
- this.#children.insert(position, [block.id]);
+ this._children.insert(position, [block.id]);
} else {
- this.#children.push([block.id]);
+ this._children.push([block.id]);
}
- await this.#set_block(block.id, content);
- this.#children_map = getMapFromYArray(this.#children);
+ await this._setBlock(block.id, content);
+ this._childrenMap = getMapFromYArray(this._children);
}
}
removeChildren(ids: (string | undefined)[]): Promise {
return new Promise(resolve => {
- if (this.#children.doc) {
- transact(this.#children.doc, () => {
+ if (this._children.doc) {
+ transact(this._children.doc, () => {
const failed = [];
for (const id of ids) {
let idx = -1;
- for (const block_id of this.#children) {
+ for (const block_id of this._children) {
idx += 1;
if (block_id === id) {
- this.#children.delete(idx);
+ this._children.delete(idx);
break;
}
}
if (id) failed.push(id);
}
- this.#children_map = getMapFromYArray(this.#children);
+ this._childrenMap = getMapFromYArray(this._children);
resolve(failed);
});
} else {
@@ -274,7 +276,7 @@ export class YjsBlockInstance implements BlockInstance {
}
public scopedHistory(scope: any[]): HistoryManager {
- return new YjsHistoryManager(this.#block, scope);
+ return new YjsHistoryManager(this._block, scope);
}
[GET_BLOCK_ITEM]() {
@@ -283,7 +285,7 @@ export class YjsBlockInstance implements BlockInstance {
return {
type: this.type,
flavor: this.flavor,
- children: this.#children.slice(),
+ children: this._children.slice(),
created: this.created,
content: this.content,
};
diff --git a/libs/datasource/jwt/src/adapter/yjs/gatekeeper.ts b/libs/datasource/jwt/src/adapter/yjs/gatekeeper.ts
index 1dd739de4f..7e02c5a09e 100644
--- a/libs/datasource/jwt/src/adapter/yjs/gatekeeper.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/gatekeeper.ts
@@ -1,36 +1,35 @@
import { Map as YMap } from 'yjs';
export class GateKeeper {
- // eslint-disable-next-line @typescript-eslint/naming-convention
- #user_id: string;
- #creators: YMap;
- #common: YMap;
+ private readonly _userId: string;
+ private readonly _creators: YMap;
+ private readonly _common: YMap;
constructor(userId: string, creators: YMap, common: YMap) {
- this.#user_id = userId;
- this.#creators = creators;
- this.#common = common;
+ this._userId = userId;
+ this._creators = creators;
+ this._common = common;
}
getCreator(block_id: string): string | undefined {
- return this.#creators.get(block_id) || this.#common.get(block_id);
+ return this._creators.get(block_id) || this._common.get(block_id);
}
setCreator(block_id: string) {
- if (!this.#creators.get(block_id)) {
- this.#creators.set(block_id, this.#user_id);
+ if (!this._creators.get(block_id)) {
+ this._creators.set(block_id, this._userId);
}
}
setCommon(block_id: string) {
- if (!this.#creators.get(block_id) && !this.#common.get(block_id)) {
- this.#common.set(block_id, this.#user_id);
+ if (!this._creators.get(block_id) && !this._common.get(block_id)) {
+ this._common.set(block_id, this._userId);
}
}
private check_delete(block_id: string): boolean {
- const creator = this.#creators.get(block_id);
- return creator === this.#user_id || !!this.#common.get(block_id);
+ const creator = this._creators.get(block_id);
+ return creator === this._userId || !!this._common.get(block_id);
}
checkDeleteLists(block_ids: string[]) {
@@ -47,7 +46,7 @@ export class GateKeeper {
}
clear() {
- this.#creators.clear();
- this.#common.clear();
+ this._creators.clear();
+ this._common.clear();
}
}
diff --git a/libs/datasource/jwt/src/adapter/yjs/history.ts b/libs/datasource/jwt/src/adapter/yjs/history.ts
index 0434d340d8..6739cf9a50 100644
--- a/libs/datasource/jwt/src/adapter/yjs/history.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/history.ts
@@ -5,34 +5,34 @@ import { HistoryCallback, HistoryManager } from '../../adapter';
type StackItem = UndoManager['undoStack'][0];
export class YjsHistoryManager implements HistoryManager {
- readonly #blocks: YMap;
- readonly #history_manager: UndoManager;
- readonly #push_listeners: Map>;
- readonly #pop_listeners: Map>;
+ private readonly _blocks: YMap;
+ private readonly _historyManager: UndoManager;
+ private readonly _pushListeners: Map>;
+ private readonly _popListeners: Map>;
constructor(scope: YMap, tracker?: any[]) {
- this.#blocks = scope;
- this.#history_manager = new UndoManager(scope, {
+ this._blocks = scope;
+ this._historyManager = new UndoManager(scope, {
trackedOrigins: tracker ? new Set(tracker) : undefined,
});
- this.#push_listeners = new Map();
- this.#history_manager.on(
+ this._pushListeners = new Map();
+ this._historyManager.on(
'stack-item-added',
(event: { stackItem: StackItem }) => {
const meta = event.stackItem.meta;
- for (const listener of this.#push_listeners.values()) {
+ for (const listener of this._pushListeners.values()) {
listener(meta);
}
}
);
- this.#pop_listeners = new Map();
- this.#history_manager.on(
+ this._popListeners = new Map();
+ this._historyManager.on(
'stack-item-popped',
(event: { stackItem: StackItem }) => {
const meta = event.stackItem.meta;
- for (const listener of this.#pop_listeners.values()) {
+ for (const listener of this._popListeners.values()) {
listener(new Map(meta));
}
}
@@ -40,19 +40,19 @@ export class YjsHistoryManager implements HistoryManager {
}
onPush(name: string, callback: HistoryCallback): void {
- this.#push_listeners.set(name, callback);
+ this._pushListeners.set(name, callback);
}
offPush(name: string): boolean {
- return this.#push_listeners.delete(name);
+ return this._pushListeners.delete(name);
}
onPop(name: string, callback: HistoryCallback): void {
- this.#pop_listeners.set(name, callback);
+ this._popListeners.set(name, callback);
}
offPop(name: string): boolean {
- return this.#pop_listeners.delete(name);
+ return this._popListeners.delete(name);
}
break(): void {
@@ -60,14 +60,14 @@ export class YjsHistoryManager implements HistoryManager {
}
undo(): Map | undefined {
- return this.#history_manager.undo()?.meta;
+ return this._historyManager.undo()?.meta;
}
redo(): Map | undefined {
- return this.#history_manager.redo()?.meta;
+ return this._historyManager.redo()?.meta;
}
clear(): void {
- return this.#history_manager.clear();
+ return this._historyManager.clear();
}
}
diff --git a/libs/datasource/jwt/src/adapter/yjs/index.ts b/libs/datasource/jwt/src/adapter/yjs/index.ts
index 1037d9e3f6..eb886afae5 100644
--- a/libs/datasource/jwt/src/adapter/yjs/index.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/index.ts
@@ -178,22 +178,22 @@ export type YjsInitOptions = {
};
export class YjsAdapter implements AsyncDatabaseAdapter {
- readonly #provider: YjsProviders;
- readonly #doc: Doc; // doc instance
- readonly #awareness: Awareness; // lightweight state synchronization
- readonly #gatekeeper: GateKeeper; // Simple access control
- readonly #history: YjsHistoryManager;
+ private readonly _provider: YjsProviders;
+ private readonly _doc: Doc; // doc instance
+ private readonly _awareness: Awareness; // lightweight state synchronization
+ private readonly _gatekeeper: GateKeeper; // Simple access control
+ private readonly _history: YjsHistoryManager;
// Block Collection
// key is a randomly generated global id
- readonly #blocks: YMap>;
- readonly #block_updated: YMap;
+ private readonly _blocks: YMap>;
+ private readonly _blockUpdated: YMap;
// Maximum cache Block 1024, ttl 10 minutes
- readonly #block_caches: LRUCache;
+ private readonly _blockCaches: LRUCache;
- readonly #binaries: YjsRemoteBinaries;
+ private readonly _binaries: YjsRemoteBinaries;
- readonly #listener: Map>;
+ private readonly _listener: Map>;
static async init(
workspace: string,
@@ -209,30 +209,30 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
}
private constructor(providers: YjsProviders) {
- this.#provider = providers;
- this.#doc = providers.idb.doc;
- this.#awareness = providers.awareness;
- this.#gatekeeper = providers.gatekeeper;
+ this._provider = providers;
+ this._doc = providers.idb.doc;
+ this._awareness = providers.awareness;
+ this._gatekeeper = providers.gatekeeper;
- const blocks = this.#doc.getMap>('blocks');
- this.#blocks =
+ const blocks = this._doc.getMap>('blocks');
+ this._blocks =
blocks.get('content') || blocks.set('content', new YMap());
- this.#block_updated =
+ this._blockUpdated =
blocks.get('updated') || blocks.set('updated', new YMap());
- this.#block_caches = new LRUCache({ max: 1024, ttl: 1000 * 60 * 10 });
- this.#binaries = new YjsRemoteBinaries(
+ this._blockCaches = new LRUCache({ max: 1024, ttl: 1000 * 60 * 10 });
+ this._binaries = new YjsRemoteBinaries(
providers.binariesIdb.doc.getMap(),
providers.remoteToken
);
- this.#history = new YjsHistoryManager(this.#blocks);
+ this._history = new YjsHistoryManager(this._blocks);
- this.#listener = new Map();
+ this._listener = new Map();
const ws = providers.ws as any;
if (ws) {
const workspace = providers.idb.name;
const emitState = (connectivity: Connectivity) => {
- this.#listener.get('connectivity')?.(
+ this._listener.get('connectivity')?.(
new Map([[workspace, connectivity]])
);
};
@@ -244,9 +244,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
const debounced_editing_notifier = debounce(
() => {
const listener: BlockListener> | undefined =
- this.#listener.get('editing');
+ this._listener.get('editing');
if (listener) {
- const mapping = this.#awareness.getStates();
+ const mapping = this._awareness.getStates();
const editing_mapping: Record = {};
for (const {
userId,
@@ -280,11 +280,11 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
{ maxWait: 1000 }
);
- this.#awareness.setLocalStateField('userId', providers.userId);
+ this._awareness.setLocalStateField('userId', providers.userId);
- this.#awareness.on('update', debounced_editing_notifier);
+ this._awareness.on('update', debounced_editing_notifier);
- this.#blocks.observeDeep(events => {
+ this._blocks.observeDeep(events => {
const now = Date.now();
const keys = events.flatMap(e => {
@@ -300,14 +300,14 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
}
});
- EmitEvents(keys, this.#listener.get('updated'));
+ EmitEvents(keys, this._listener.get('updated'));
- transact(this.#doc, () => {
+ transact(this._doc, () => {
for (const [key, action] of keys) {
if (action === 'delete') {
- this.#block_updated.delete(key);
+ this._blockUpdated.delete(key);
} else {
- this.#block_updated.set(key, now);
+ this._blockUpdated.set(key, now);
}
}
});
@@ -315,7 +315,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
}
getUserId(): string {
- return this.#provider.userId;
+ return this._provider.userId;
}
inspector() {
@@ -333,7 +333,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
return {
save: () => {
- const binary = encodeStateAsUpdate(this.#doc);
+ const binary = encodeStateAsUpdate(this._doc);
saveAs(
new Blob([binary]),
`affine_workspace_${new Date().toDateString()}.apk`
@@ -353,7 +353,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
});
const [file] = (await fromEvent(handles)) as File[];
const binary = await file.arrayBuffer();
- await this.#provider.idb.clearData();
+ await this._provider.idb.clearData();
const doc = new Doc({ autoLoad: true, shouldLoad: true });
let updated = 0;
let isUpdated = false;
@@ -374,21 +374,21 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
};
check();
});
- await new IndexeddbPersistence(this.#provider.idb.name, doc)
+ await new IndexeddbPersistence(this._provider.idb.name, doc)
.whenSynced;
applyUpdate(doc, new Uint8Array(binary));
await update_check;
console.log('load success');
},
- parse: () => this.#doc.toJSON(),
+ parse: () => this._doc.toJSON(),
// eslint-disable-next-line @typescript-eslint/naming-convention
parse_page: (page_id: string) => {
- const blocks = this.#blocks.toJSON();
+ const blocks = this._blocks.toJSON();
return resolve_block(blocks, page_id);
},
// eslint-disable-next-line @typescript-eslint/naming-convention
parse_pages: (resolve = false) => {
- const blocks = this.#blocks.toJSON();
+ const blocks = this._blocks.toJSON();
return Object.fromEntries(
Object.entries(blocks)
.filter(([, block]) => block.flavor === 'page')
@@ -402,21 +402,21 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
);
},
clear: () => {
- this.#blocks.clear();
- this.#block_updated.clear();
- this.#gatekeeper.clear();
- this.#doc.getMap('blocks').clear();
- this.#doc.getMap('gatekeeper').clear();
+ this._blocks.clear();
+ this._blockUpdated.clear();
+ this._gatekeeper.clear();
+ this._doc.getMap('blocks').clear();
+ this._doc.getMap('gatekeeper').clear();
},
// eslint-disable-next-line @typescript-eslint/naming-convention
clear_old: () => {
- this.#doc.getMap('block_updated').clear();
- this.#doc.getMap('blocks').clear();
- this.#doc.getMap('common').clear();
- this.#doc.getMap('creators').clear();
+ this._doc.getMap('block_updated').clear();
+ this._doc.getMap('blocks').clear();
+ this._doc.getMap('common').clear();
+ this._doc.getMap('creators').clear();
},
snapshot: () => {
- return snapshot(this.#doc);
+ return snapshot(this._doc);
},
};
}
@@ -459,15 +459,15 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
}
private get_updated(id: string) {
- return this.#block_updated.get(id);
+ return this._blockUpdated.get(id);
}
private get_creator(id: string) {
- return this.#gatekeeper.getCreator(id);
+ return this._gatekeeper.getCreator(id);
}
private get_block_sync(id: string): YjsBlockInstance | undefined {
- const cached = this.#block_caches.get(id);
+ const cached = this._blockCaches.get(id);
if (cached) {
// Synchronous read cannot read binary
if (cached.type === BlockTypes.block) {
@@ -476,7 +476,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
return undefined;
}
- const block = this.#blocks.get(id);
+ const block = this._blocks.get(id);
// Synchronous read cannot read binary
if (block && block.get('type') === BlockTypes.block) {
@@ -496,9 +496,9 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
async getBlock(id: string): Promise {
const block_instance = this.get_block_sync(id);
if (block_instance) return block_instance;
- const block = this.#blocks.get(id);
+ const block = this._blocks.get(id);
if (block && block.get('type') === BlockTypes.binary) {
- const binary = await this.#binaries.get(
+ const binary = await this._binaries.get(
block.get('hash') as string
);
if (binary) {
@@ -520,7 +520,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
flavor: BlockItem['flavor']
): Promise {
const keys: string[] = [];
- this.#blocks.forEach((doc, key) => {
+ this._blocks.forEach((doc, key) => {
if (doc.get('flavor') === flavor) {
keys.push(key);
}
@@ -533,7 +533,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
type: BlockItem['type']
): Promise {
const keys: string[] = [];
- this.#blocks.forEach((doc, key) => {
+ this._blocks.forEach((doc, key) => {
if (doc.get('type') === type) {
keys.push(key);
}
@@ -547,8 +547,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
item: BlockItem & { hash?: string }
): Promise {
return new Promise((resolve, reject) => {
- const block = this.#blocks.get(key) || new YMap();
- transact(this.#doc, () => {
+ const block = this._blocks.get(key) || new YMap();
+ transact(this._doc, () => {
// Insert only if the block doesn't exist yet
// Other modification operations are done in the block instance
let uploaded: Promise | undefined;
@@ -568,8 +568,8 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
} else if (item.type === BlockTypes.binary && item.hash) {
if (content instanceof YArray) {
block.set('hash', item.hash);
- if (!this.#binaries.has(item.hash)) {
- uploaded = this.#binaries.set(
+ if (!this._binaries.has(item.hash)) {
+ uploaded = this._binaries.set(
item.hash,
content
);
@@ -583,18 +583,18 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
throw new Error('invalid block type: ' + item.type);
}
- this.#blocks.set(key, block);
+ this._blocks.set(key, block);
}
if (item.flavor === 'page') {
- this.#awareness.setLocalStateField('editing', key);
- this.#awareness.setLocalStateField('updated', Date.now());
+ this._awareness.setLocalStateField('editing', key);
+ this._awareness.setLocalStateField('updated', Date.now());
}
// References do not add delete restrictions
if (item.flavor === 'reference') {
- this.#gatekeeper.setCommon(key);
+ this._gatekeeper.setCommon(key);
} else {
- this.#gatekeeper.setCreator(key);
+ this._gatekeeper.setCreator(key);
}
if (uploaded) {
@@ -613,15 +613,15 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
async checkBlocks(keys: string[]): Promise {
return (
- keys.filter(key => !!this.#blocks.get(key)).length === keys.length
+ keys.filter(key => !!this._blocks.get(key)).length === keys.length
);
}
async deleteBlocks(keys: string[]): Promise {
- const [success, fail] = this.#gatekeeper.checkDeleteLists(keys);
- transact(this.#doc, () => {
+ const [success, fail] = this._gatekeeper.checkDeleteLists(keys);
+ transact(this._doc, () => {
for (const key of success) {
- this.#blocks.delete(key);
+ this._blocks.delete(key);
}
});
return fail;
@@ -631,7 +631,7 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
key: 'editing' | 'updated' | 'connectivity',
listener: BlockListener
): void {
- this.#listener.set(key, listener);
+ this._listener.set(key, listener);
}
suspend(suspend: boolean) {
@@ -639,6 +639,6 @@ export class YjsAdapter implements AsyncDatabaseAdapter {
}
public history(): HistoryManager {
- return this.#history;
+ return this._history;
}
}
diff --git a/libs/datasource/jwt/src/adapter/yjs/operation.ts b/libs/datasource/jwt/src/adapter/yjs/operation.ts
index 8cb825ad3b..a8dfdaffa4 100644
--- a/libs/datasource/jwt/src/adapter/yjs/operation.ts
+++ b/libs/datasource/jwt/src/adapter/yjs/operation.ts
@@ -52,18 +52,18 @@ function auto_set(root: ContentOperation, key: string, data: BaseTypes): void {
}
export class YjsContentOperation implements ContentOperation {
- readonly #content: YAbstractType;
+ private readonly _content: YAbstractType;
constructor(content: YAbstractType) {
- this.#content = content;
+ this._content = content;
}
get length(): number {
- if (this.#content instanceof YMap) {
- return this.#content.size;
+ if (this._content instanceof YMap) {
+ return this._content.size;
}
- if (this.#content instanceof YArray || this.#content instanceof YText) {
- return this.#content.length;
+ if (this._content instanceof YArray || this._content instanceof YText) {
+ return this._content.length;
}
return 0;
}
@@ -83,8 +83,8 @@ export class YjsContentOperation implements ContentOperation {
}
asText(): YjsTextOperation | undefined {
- if (this.#content instanceof YText) {
- return new YjsTextOperation(this.#content);
+ if (this._content instanceof YText) {
+ return new YjsTextOperation(this._content);
}
return undefined;
}
@@ -92,8 +92,8 @@ export class YjsContentOperation implements ContentOperation {
asArray():
| YjsArrayOperation
| undefined {
- if (this.#content instanceof YArray) {
- return new YjsArrayOperation(this.#content);
+ if (this._content instanceof YArray) {
+ return new YjsArrayOperation(this._content);
}
return undefined;
}
@@ -101,8 +101,8 @@ export class YjsContentOperation implements ContentOperation {
asMap():
| YjsMapOperation
| undefined {
- if (this.#content instanceof YMap) {
- return new YjsMapOperation(this.#content);
+ if (this._content instanceof YMap) {
+ return new YjsMapOperation(this._content);
}
return undefined;
}
@@ -184,24 +184,24 @@ export class YjsContentOperation implements ContentOperation {
}
[INTO_INNER](): YAbstractType | undefined {
- if (this.#content instanceof YAbstractType) {
- return this.#content;
+ if (this._content instanceof YAbstractType) {
+ return this._content;
}
return undefined;
}
// eslint-disable-next-line @typescript-eslint/naming-convention
private toJSON() {
- return this.#content.toJSON();
+ return this._content.toJSON();
}
}
class YjsTextOperation extends YjsContentOperation implements TextOperation {
- readonly #content: YText;
+ private readonly _textContent: YText;
constructor(content: YText) {
super(content);
- this.#content = content;
+ this._textContent = content;
}
insert(
@@ -209,7 +209,7 @@ class YjsTextOperation extends YjsContentOperation implements TextOperation {
content: string,
format?: Record
): void {
- this.#content.insert(index, content, format);
+ this._textContent.insert(index, content, format);
}
format(
@@ -217,23 +217,23 @@ class YjsTextOperation extends YjsContentOperation implements TextOperation {
length: number,
format: Record
): void {
- this.#content.format(index, length, format);
+ this._textContent.format(index, length, format);
}
delete(index: number, length: number): void {
- this.#content.delete(index, length);
+ this._textContent.delete(index, length);
}
setAttribute(name: string, value: BaseTypes) {
- this.#content.setAttribute(name, value);
+ this._textContent.setAttribute(name, value);
}
getAttribute(name: string): T | undefined {
- return this.#content.getAttribute(name);
+ return this._textContent.getAttribute(name);
}
override toString(): TextToken[] {
- return this.#content.toDelta();
+ return this._textContent.toDelta();
}
}
@@ -241,67 +241,69 @@ class YjsArrayOperation
extends YjsContentOperation
implements ArrayOperation
{
- readonly #content: YArray;
- readonly #listeners: Map;
+ private readonly _arrayContent: YArray;
+ private readonly _listeners: Map;
constructor(content: YArray) {
super(content);
- this.#content = content;
- this.#listeners = new Map();
+ this._arrayContent = content;
+ this._listeners = new Map();
- this.#content.observe(event =>
- ChildrenListenerHandler(this.#listeners, event)
+ this._arrayContent.observe(event =>
+ ChildrenListenerHandler(this._listeners, event)
);
}
on(name: string, listener: BlockListener) {
- this.#listeners.set(name, listener);
+ this._listeners.set(name, listener);
}
off(name: string) {
- this.#listeners.delete(name);
+ this._listeners.delete(name);
}
insert(index: number, content: Array>): void {
- this.#content.insert(
+ this._arrayContent.insert(
index,
content.map(v => this.into_inner(v))
);
}
delete(index: number, length: number): void {
- this.#content.delete(index, length);
+ this._arrayContent.delete(index, length);
}
push(content: Array>): void {
- this.#content.push(content.map(v => this.into_inner(v)));
+ this._arrayContent.push(content.map(v => this.into_inner(v)));
}
unshift(content: Array>): void {
- this.#content.unshift(content.map(v => this.into_inner(v)));
+ this._arrayContent.unshift(content.map(v => this.into_inner(v)));
}
get(index: number): Operable | undefined {
- const content = this.#content.get(index);
+ const content = this._arrayContent.get(index);
if (content) return this.to_operable(content);
return undefined;
}
private get_internal(index: number): T {
- return this.#content.get(index);
+ return this._arrayContent.get(index);
}
slice(start?: number, end?: number): Operable[] {
- return this.#content.slice(start, end).map(v => this.to_operable(v));
+ return this._arrayContent
+ .slice(start, end)
+ .map(v => this.to_operable(v));
}
map(callback: (value: T, index: number) => R): R[] {
- return this.#content.map((value, index) => callback(value, index));
+ return this._arrayContent.map((value, index) => callback(value, index));
}
// Traverse, if callback returns false, stop traversing
forEach(callback: (value: T, index: number) => boolean) {
- for (let i = 0; i < this.#content.length; i++) {
+ for (let i = 0; i < this._arrayContent.length; i++) {
const ret = callback(this.get_internal(i), i);
if (ret === false) {
break;
@@ -342,47 +344,47 @@ class YjsMapOperation
extends YjsContentOperation
implements MapOperation
{
- readonly #content: YMap;
- readonly #listeners: Map;
+ private readonly _mapContent: YMap;
+ private readonly _listeners: Map;
constructor(content: YMap) {
super(content);
- this.#content = content;
- this.#listeners = new Map();
+ this._mapContent = content;
+ this._listeners = new Map();
content?.observeDeep(events =>
- ContentListenerHandler(this.#listeners, events)
+ ContentListenerHandler(this._listeners, events)
);
}
on(name: string, listener: BlockListener) {
- this.#listeners.set(name, listener);
+ this._listeners.set(name, listener);
}
off(name: string) {
- this.#listeners.delete(name);
+ this._listeners.delete(name);
}
set(key: string, value: Operable): void {
if (value instanceof YjsContentOperation) {
const content = value[INTO_INNER]();
- if (content) this.#content.set(key, content as unknown as T);
+ if (content) this._mapContent.set(key, content as unknown as T);
} else {
- this.#content.set(key, value as T);
+ this._mapContent.set(key, value as T);
}
}
get(key: string): Operable | undefined {
- const content = this.#content.get(key);
+ const content = this._mapContent.get(key);
if (content) return this.to_operable(content);
return undefined;
}
delete(key: string): void {
- this.#content.delete(key);
+ this._mapContent.delete(key);
}
has(key: string): boolean {
- return this.#content.has(key);
+ return this._mapContent.has(key);
}
}
diff --git a/libs/datasource/jwt/src/block/abstract.ts b/libs/datasource/jwt/src/block/abstract.ts
index 30da2ffc57..29ee655dc2 100644
--- a/libs/datasource/jwt/src/block/abstract.ts
+++ b/libs/datasource/jwt/src/block/abstract.ts
@@ -26,47 +26,47 @@ export class AbstractBlock<
B extends BlockInstance,
C extends ContentOperation
> {
- readonly #id: string;
+ private readonly _id: string;
readonly #block: BlockInstance;
- readonly #history: HistoryManager;
- readonly #root?: AbstractBlock;
- readonly #parent_listener: Map;
+ private readonly _history: HistoryManager;
+ private readonly _root?: AbstractBlock;
+ private readonly _parentListener: Map;
- #parent?: AbstractBlock;
+ _parent?: AbstractBlock