mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-18 19:51:38 +03:00
Merge remote-tracking branch 'origin/master' into develop
This commit is contained in:
commit
812af254aa
12
README.md
12
README.md
@ -40,17 +40,19 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
|
||||
|
||||
<p align="center"><img width="1920" alt="affine_screen" src="https://user-images.githubusercontent.com/21084335/182552060-972cac0e-6258-4ccb-85bd-3bb466c30ccd.png"><p/>
|
||||
|
||||
# Stay Up-to-Date
|
||||
# Stay Up-to-Date and Support Us
|
||||
|
||||
![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif)
|
||||
|
||||
# How to use
|
||||
|
||||
If you have experience in front-end development, please [refer to here](https://affine.gitbook.io/affine/basic-documentation/contribute-to-affine); if you want to experience our latest version, please wait a moment, we will launch a web version in the near future
|
||||
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)
|
||||
@ -89,7 +91,7 @@ Affine is fully built with web technologies so that consistency and accessibilit
|
||||
|
||||
# Documentation
|
||||
|
||||
Please view the [documentation](https://affine.gitbook.io/affine/)
|
||||
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
|
||||
|
||||
@ -121,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.
|
||||
|
@ -33,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 (
|
||||
<LigoApp>
|
||||
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>
|
||||
|
@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => {
|
||||
const { addCard } = useKanban();
|
||||
const handleClick = useCallback(async () => {
|
||||
await addCard(group);
|
||||
}, [addCard]);
|
||||
}, [addCard, group]);
|
||||
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
|
||||
};
|
||||
|
||||
|
@ -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 (
|
||||
<CardContainer>
|
||||
<CardContainer onClick={onClickCard}>
|
||||
<CardContent>
|
||||
<RenderBlock blockId={id} />
|
||||
</CardContent>
|
||||
<CardActions onClick={onAddItem}>
|
||||
<PlusIcon />
|
||||
<span>Add item</span>
|
||||
<span>Add a sub-block</span>
|
||||
</CardActions>
|
||||
</CardContainer>
|
||||
);
|
||||
|
@ -143,13 +143,13 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
|
||||
type: 'link',
|
||||
});
|
||||
};
|
||||
const handle_click = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
const handle_click = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||
//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) {
|
||||
|
@ -99,7 +99,7 @@ export const TextView: FC<CreateTextView> = ({
|
||||
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<CreateTextView> = ({
|
||||
'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;
|
||||
}
|
||||
}
|
||||
|
@ -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<number>();
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div
|
||||
onMouseDown={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
style={{ display: show ? 'block' : 'none', height: '100%' }}
|
||||
>
|
||||
<iframe src={src} onLoad={onLoad} />
|
||||
</div>
|
||||
{!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>loading...</Loading>;
|
||||
};
|
||||
|
||||
export const SourceView: FC<Props> = 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 (
|
||||
<LinkContainer
|
||||
isSelected={isSelected}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
onClick={openTabOnBrowser}
|
||||
>
|
||||
<p>{getHost(src)}</p>
|
||||
<p>{src}</p>
|
||||
</LinkContainer>
|
||||
<div style={{ display: 'flex' }}>
|
||||
<SourceViewContainer isSelected={isSelected} scene={type}>
|
||||
<MouseMaskContainer />
|
||||
|
||||
<LazyIframe
|
||||
src={src}
|
||||
fallback={LoadingContiner()}
|
||||
></LazyIframe>
|
||||
</SourceViewContainer>
|
||||
</div>
|
||||
);
|
||||
} else if (src?.startsWith('affine')) {
|
||||
return (
|
||||
<SourceViewContainer
|
||||
isSelected={isSelected}
|
||||
style={{ padding: '0' }}
|
||||
scene={type}
|
||||
>
|
||||
<BlockPreview
|
||||
block={block}
|
||||
|
@ -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<AsyncBlock>(null as any);
|
||||
|
||||
/**
|
||||
* Context of column information
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
export const ColumnsContext = createContext<{
|
||||
fromId: string;
|
||||
columns: Column[];
|
||||
}>({
|
||||
fromId: '',
|
||||
columns: [],
|
||||
});
|
@ -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<PropsWithChildren<RenderRootProps>> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<RootContext.Provider value={{ editor, editorElement }}>
|
||||
<EditorProvider value={{ editor, editorElement }}>
|
||||
<Container
|
||||
isWhiteboard={editor.isWhiteboard}
|
||||
ref={ref => {
|
||||
@ -183,7 +183,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
|
||||
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
|
||||
{patchedNodes}
|
||||
</Container>
|
||||
</RootContext.Provider>
|
||||
</EditorProvider>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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]
|
||||
);
|
||||
|
@ -367,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) {
|
||||
|
@ -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<AsyncBlock>();
|
||||
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 = <T extends keyof SelectionSettingsMap>(
|
||||
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 = <T extends keyof SelectionSettingsMap>(
|
||||
* @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 () => {
|
||||
|
@ -1,4 +1,3 @@
|
||||
export { ColumnsContext, RootContext } from './contexts';
|
||||
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
|
||||
export * from './render-block';
|
||||
export * from './hooks';
|
||||
@ -16,3 +15,5 @@ export * from './kanban/types';
|
||||
export * from './utils';
|
||||
|
||||
export * from './editor';
|
||||
|
||||
export { RefPageProvider, useRefPage } from './ref-page';
|
||||
|
@ -57,18 +57,13 @@ const isValueBelongOption = (
|
||||
option: KanbanGroup
|
||||
) => {
|
||||
switch (propertyValue.type) {
|
||||
case PropertyType.Select || PropertyType.Status: {
|
||||
case PropertyType.Select:
|
||||
case PropertyType.Status: {
|
||||
return propertyValue.value === option.id;
|
||||
}
|
||||
case PropertyType.MultiSelect: {
|
||||
return propertyValue.value.some(i => i === option.id);
|
||||
}
|
||||
// case PropertyType.Status: {
|
||||
// return propertyValue.value === option.id;
|
||||
// }
|
||||
// case PropertyType.Text: {
|
||||
// TOTODO:DO support this type
|
||||
// }
|
||||
default: {
|
||||
console.error(propertyValue, option);
|
||||
throw new Error('Not support group by type');
|
||||
|
@ -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';
|
||||
|
@ -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 (
|
||||
<RecastBlockContext.Provider value={block}>
|
||||
{children}
|
||||
<RefPageProvider>{children}</RefPageProvider>
|
||||
</RecastBlockContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -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;
|
||||
|
@ -49,22 +49,3 @@ const SomeBlock = () => {
|
||||
return <div>...</div>;
|
||||
};
|
||||
```
|
||||
|
||||
## Scene
|
||||
|
||||
**Notice: The scene API will refactor at next version.**
|
||||
|
||||
```tsx
|
||||
const SomeBlock = () => {
|
||||
const { scene, setScene, setPage, setTable, setKanban } =
|
||||
useRecastBlockScene();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>Scene: {scene}</div>
|
||||
<button onClick={setPage}>list</button>
|
||||
<button onClick={setKanban}>kanban</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
@ -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,
|
||||
|
@ -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<HTMLElement>
|
||||
) => {
|
||||
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 =
|
||||
|
90
libs/components/editor-core/src/ref-page/ModalPage.tsx
Normal file
90
libs/components/editor-core/src/ref-page/ModalPage.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui';
|
||||
import { createContext, ReactNode, useContext, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useEditor } from '../Contexts';
|
||||
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(
|
||||
<MuiBackdrop
|
||||
open={open}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
background: 'rgba(58, 76, 92, 0.4)',
|
||||
zIndex: theme.affine.zIndex.popover,
|
||||
}}
|
||||
onClick={closeSubPage}
|
||||
>
|
||||
<Dialog
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Dialog>
|
||||
</MuiBackdrop>,
|
||||
|
||||
document.body
|
||||
);
|
||||
};
|
||||
|
||||
const ModalPage = ({ blockId }: { blockId: string | null }) => {
|
||||
const { editor } = useEditor();
|
||||
|
||||
return (
|
||||
<Modal open={!!blockId}>
|
||||
{blockId && <RenderBlock blockId={blockId} />}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const RefPageContext = createContext<
|
||||
ReturnType<typeof useState<string | null>> | undefined
|
||||
>(undefined);
|
||||
|
||||
export const RefPageProvider = ({ children }: { children: ReactNode }) => {
|
||||
const state = useState<string | null>();
|
||||
const [blockId, setBlockId] = state;
|
||||
|
||||
return (
|
||||
<RefPageContext.Provider value={state}>
|
||||
{children}
|
||||
<ModalPage blockId={blockId ?? null} />
|
||||
</RefPageContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
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 = () => {};
|
1
libs/components/editor-core/src/ref-page/index.ts
Normal file
1
libs/components/editor-core/src/ref-page/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { useRefPage, RefPageProvider } from './ModalPage';
|
@ -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<RenderBlockProps> = ({
|
||||
blockId,
|
||||
hasContainer = true,
|
||||
}) => {
|
||||
const { editor, editorElement } = useContext(RootContext);
|
||||
const { editor, editorElement } = useEditor();
|
||||
const { block } = useBlock(blockId);
|
||||
const blockRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
@ -242,25 +242,29 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
|
||||
onKeyUpCapture={handleKeyup}
|
||||
ref={commandMenuContentRef}
|
||||
>
|
||||
<MuiClickAwayListener onClickAway={handleClickAway}>
|
||||
<div>
|
||||
<CommandMenuContainer
|
||||
editor={editor}
|
||||
hooks={hooks}
|
||||
style={{
|
||||
...commandMenuPosition,
|
||||
...style,
|
||||
}}
|
||||
isShow={show}
|
||||
blockId={blockId}
|
||||
onSelected={handleSelected}
|
||||
onclose={handleClose}
|
||||
searchBlocks={searchBlocks}
|
||||
types={types}
|
||||
categories={categories}
|
||||
/>
|
||||
</div>
|
||||
</MuiClickAwayListener>
|
||||
{show ? (
|
||||
<MuiClickAwayListener onClickAway={handleClickAway}>
|
||||
<div>
|
||||
<CommandMenuContainer
|
||||
editor={editor}
|
||||
hooks={hooks}
|
||||
style={{
|
||||
...commandMenuPosition,
|
||||
...style,
|
||||
}}
|
||||
isShow={show}
|
||||
blockId={blockId}
|
||||
onSelected={handleSelected}
|
||||
onclose={handleClose}
|
||||
searchBlocks={searchBlocks}
|
||||
types={types}
|
||||
categories={categories}
|
||||
/>
|
||||
</div>
|
||||
</MuiClickAwayListener>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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',
|
||||
|
@ -64,30 +64,32 @@ export const Activities = () => {
|
||||
const [recentPages, setRecentPages] = useState([]);
|
||||
const userId = user?.id;
|
||||
|
||||
/* temporarily remove:show recently viewed documents */
|
||||
const fetchRecentPages = useCallback(async () => {
|
||||
/* show recently edit documents */
|
||||
const getRecentEditPages = useCallback(async () => {
|
||||
if (!userId || !currentSpaceId) {
|
||||
return;
|
||||
}
|
||||
const recent_pages = await services.api.userConfig.getRecentPages(
|
||||
currentSpaceId,
|
||||
userId
|
||||
);
|
||||
setRecentPages(recent_pages);
|
||||
}, [userId, currentSpaceId]);
|
||||
|
||||
const recentEditPages =
|
||||
(await services.api.userConfig.getRecentEditedPages(
|
||||
currentSpaceId
|
||||
)) || [];
|
||||
|
||||
setRecentPages(recentEditPages);
|
||||
}, [currentSpaceId, userId]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await fetchRecentPages();
|
||||
await getRecentEditPages();
|
||||
})();
|
||||
}, [fetchRecentPages]);
|
||||
}, [getRecentEditPages]);
|
||||
|
||||
useEffect(() => {
|
||||
let unobserve: () => void;
|
||||
const observe = async () => {
|
||||
unobserve = await services.api.userConfig.observe(
|
||||
{ workspace: currentSpaceId },
|
||||
fetchRecentPages
|
||||
getRecentEditPages
|
||||
);
|
||||
};
|
||||
observe();
|
||||
@ -95,12 +97,13 @@ export const Activities = () => {
|
||||
return () => {
|
||||
unobserve?.();
|
||||
};
|
||||
}, [currentSpaceId, fetchRecentPages]);
|
||||
}, [currentSpaceId, getRecentEditPages]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<List style={{ padding: '0px' }}>
|
||||
{recentPages.map(({ id, title, lastOpenTime }) => {
|
||||
{recentPages.map(item => {
|
||||
const { id, title, updated } = item;
|
||||
return (
|
||||
<ListItem className="item" key={id}>
|
||||
<StyledItemContent
|
||||
@ -114,7 +117,7 @@ export const Activities = () => {
|
||||
/>
|
||||
<ListItemText
|
||||
className="itemRight"
|
||||
primary={formatDistanceToNow(lastOpenTime, {
|
||||
primary={formatDistanceToNow(updated, {
|
||||
includeSeconds: true,
|
||||
})}
|
||||
/>
|
||||
|
@ -13,6 +13,7 @@ import type {
|
||||
} from '@mui/material';
|
||||
import {
|
||||
Avatar,
|
||||
Backdrop,
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
@ -243,3 +244,7 @@ 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;
|
||||
|
@ -3,6 +3,7 @@ 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 {
|
||||
@ -122,4 +123,20 @@ export class UserConfig extends ServiceBaseClass {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
// },
|
||||
// ],
|
||||
};
|
||||
|
@ -3,8 +3,8 @@ import { Array as YArray, Map as YMap } from 'yjs';
|
||||
import { RemoteKvService } from '@toeverything/datasource/remote-kv';
|
||||
|
||||
export class YjsRemoteBinaries {
|
||||
readonly _binaries: YMap<YArray<ArrayBuffer>>; // binary instance
|
||||
readonly _remoteStorage?: RemoteKvService;
|
||||
private readonly _binaries: YMap<YArray<ArrayBuffer>>; // binary instance
|
||||
private readonly _remoteStorage?: RemoteKvService;
|
||||
|
||||
constructor(binaries: YMap<YArray<ArrayBuffer>>, remote_token?: string) {
|
||||
this._binaries = binaries;
|
||||
|
@ -32,19 +32,21 @@ type YjsBlockInstanceProps = {
|
||||
};
|
||||
|
||||
export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
|
||||
readonly _id: string;
|
||||
readonly _block: YMap<unknown>;
|
||||
readonly _binary?: YArray<ArrayBuffer>;
|
||||
readonly _children: YArray<string>;
|
||||
readonly _setBlock: (
|
||||
private readonly _id: string;
|
||||
private readonly _block: YMap<unknown>;
|
||||
private readonly _binary?: YArray<ArrayBuffer>;
|
||||
private readonly _children: YArray<string>;
|
||||
private readonly _setBlock: (
|
||||
id: string,
|
||||
block: BlockItem<YjsContentOperation>
|
||||
) => Promise<void>;
|
||||
readonly _getUpdated: (id: string) => number | undefined;
|
||||
readonly _getCreator: (id: string) => string | undefined;
|
||||
readonly _getBlockInstance: (id: string) => YjsBlockInstance | undefined;
|
||||
readonly _childrenListeners: Map<string, BlockListener>;
|
||||
readonly _contentListeners: Map<string, BlockListener>;
|
||||
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<string, BlockListener>;
|
||||
private readonly _contentListeners: Map<string, BlockListener>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_childrenMap: Map<string, number>;
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { Map as YMap } from 'yjs';
|
||||
|
||||
export class GateKeeper {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
_userId: string;
|
||||
_creators: YMap<string>;
|
||||
_common: YMap<string>;
|
||||
private readonly _userId: string;
|
||||
private readonly _creators: YMap<string>;
|
||||
private readonly _common: YMap<string>;
|
||||
|
||||
constructor(userId: string, creators: YMap<string>, common: YMap<string>) {
|
||||
this._userId = userId;
|
||||
|
@ -5,10 +5,10 @@ import { HistoryCallback, HistoryManager } from '../../adapter';
|
||||
type StackItem = UndoManager['undoStack'][0];
|
||||
|
||||
export class YjsHistoryManager implements HistoryManager {
|
||||
readonly _blocks: YMap<any>;
|
||||
readonly _historyManager: UndoManager;
|
||||
readonly _pushListeners: Map<string, HistoryCallback<any>>;
|
||||
readonly _popListeners: Map<string, HistoryCallback<any>>;
|
||||
private readonly _blocks: YMap<any>;
|
||||
private readonly _historyManager: UndoManager;
|
||||
private readonly _pushListeners: Map<string, HistoryCallback<any>>;
|
||||
private readonly _popListeners: Map<string, HistoryCallback<any>>;
|
||||
|
||||
constructor(scope: YMap<any>, tracker?: any[]) {
|
||||
this._blocks = scope;
|
||||
|
@ -178,22 +178,22 @@ export type YjsInitOptions = {
|
||||
};
|
||||
|
||||
export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
|
||||
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<YMap<unknown>>;
|
||||
readonly _blockUpdated: YMap<number>;
|
||||
private readonly _blocks: YMap<YMap<unknown>>;
|
||||
private readonly _blockUpdated: YMap<number>;
|
||||
// Maximum cache Block 1024, ttl 10 minutes
|
||||
readonly _blockCaches: LRUCache<string, YjsBlockInstance>;
|
||||
private readonly _blockCaches: LRUCache<string, YjsBlockInstance>;
|
||||
|
||||
readonly _binaries: YjsRemoteBinaries;
|
||||
private readonly _binaries: YjsRemoteBinaries;
|
||||
|
||||
readonly _listener: Map<string, BlockListener<any>>;
|
||||
private readonly _listener: Map<string, BlockListener<any>>;
|
||||
|
||||
static async init(
|
||||
workspace: string,
|
||||
|
@ -52,7 +52,7 @@ function auto_set(root: ContentOperation, key: string, data: BaseTypes): void {
|
||||
}
|
||||
|
||||
export class YjsContentOperation implements ContentOperation {
|
||||
readonly _content: YAbstractType<unknown>;
|
||||
private readonly _content: YAbstractType<unknown>;
|
||||
|
||||
constructor(content: YAbstractType<any>) {
|
||||
this._content = content;
|
||||
@ -197,7 +197,7 @@ export class YjsContentOperation implements ContentOperation {
|
||||
}
|
||||
|
||||
class YjsTextOperation extends YjsContentOperation implements TextOperation {
|
||||
readonly _textContent: YText;
|
||||
private readonly _textContent: YText;
|
||||
|
||||
constructor(content: YText) {
|
||||
super(content);
|
||||
@ -241,8 +241,8 @@ class YjsArrayOperation<T extends ContentTypes>
|
||||
extends YjsContentOperation
|
||||
implements ArrayOperation<T>
|
||||
{
|
||||
readonly _arrayContent: YArray<T>;
|
||||
readonly _listeners: Map<string, BlockListener>;
|
||||
private readonly _arrayContent: YArray<T>;
|
||||
private readonly _listeners: Map<string, BlockListener>;
|
||||
|
||||
constructor(content: YArray<T>) {
|
||||
super(content);
|
||||
@ -344,8 +344,8 @@ class YjsMapOperation<T extends ContentTypes>
|
||||
extends YjsContentOperation
|
||||
implements MapOperation<T>
|
||||
{
|
||||
readonly _mapContent: YMap<T>;
|
||||
readonly _listeners: Map<string, BlockListener>;
|
||||
private readonly _mapContent: YMap<T>;
|
||||
private readonly _listeners: Map<string, BlockListener>;
|
||||
|
||||
constructor(content: YMap<T>) {
|
||||
super(content);
|
||||
|
@ -26,11 +26,11 @@ export class AbstractBlock<
|
||||
B extends BlockInstance<C>,
|
||||
C extends ContentOperation
|
||||
> {
|
||||
readonly _id: string;
|
||||
private readonly _id: string;
|
||||
readonly #block: BlockInstance<C>;
|
||||
readonly _history: HistoryManager;
|
||||
readonly _root?: AbstractBlock<B, C>;
|
||||
readonly _parentListener: Map<string, BlockListener>;
|
||||
private readonly _history: HistoryManager;
|
||||
private readonly _root?: AbstractBlock<B, C>;
|
||||
private readonly _parentListener: Map<string, BlockListener>;
|
||||
|
||||
_parent?: AbstractBlock<B, C>;
|
||||
|
||||
|
@ -51,19 +51,19 @@ export class BaseBlock<
|
||||
B extends BlockInstance<C>,
|
||||
C extends ContentOperation
|
||||
> extends AbstractBlock<B, C> {
|
||||
readonly _exporters?: Exporters;
|
||||
readonly _contentExportersGetter: () => Map<
|
||||
private readonly _exporters?: Exporters;
|
||||
private readonly _contentExportersGetter: () => Map<
|
||||
string,
|
||||
ReadableContentExporter<string, any>
|
||||
>;
|
||||
readonly _metadataExportersGetter: () => Map<
|
||||
private readonly _metadataExportersGetter: () => Map<
|
||||
string,
|
||||
ReadableContentExporter<
|
||||
Array<[string, number | string | string[]]>,
|
||||
any
|
||||
>
|
||||
>;
|
||||
readonly _tagExportersGetter: () => Map<
|
||||
private readonly _tagExportersGetter: () => Map<
|
||||
string,
|
||||
ReadableContentExporter<string[], any>
|
||||
>;
|
||||
|
@ -107,18 +107,18 @@ export class BlockIndexer<
|
||||
B extends BlockInstance<C>,
|
||||
C extends ContentOperation
|
||||
> {
|
||||
readonly _adapter: A;
|
||||
readonly _idb: BlockIdbInstance;
|
||||
private readonly _adapter: A;
|
||||
private readonly _idb: BlockIdbInstance;
|
||||
|
||||
readonly _blockIndexer: DocumentIndexer<IndexMetadata>;
|
||||
readonly _blockMetadata: LRUCache<string, QueryMetadata>;
|
||||
readonly _eventBus: BlockEventBus;
|
||||
private readonly _blockIndexer: DocumentIndexer<IndexMetadata>;
|
||||
private readonly _blockMetadata: LRUCache<string, QueryMetadata>;
|
||||
private readonly _eventBus: BlockEventBus;
|
||||
|
||||
readonly _blockBuilder: (
|
||||
private readonly _blockBuilder: (
|
||||
block: BlockInstance<C>
|
||||
) => Promise<BaseBlock<B, C>>;
|
||||
|
||||
readonly _delayIndex: { documents: Map<string, BaseBlock<B, C>> };
|
||||
private readonly _delayIndex: { documents: Map<string, BaseBlock<B, C>> };
|
||||
|
||||
constructor(
|
||||
adapter: A,
|
||||
|
@ -69,14 +69,14 @@ export class BlockClient<
|
||||
B extends BlockInstance<C>,
|
||||
C extends ContentOperation
|
||||
> {
|
||||
readonly _adapter: A;
|
||||
readonly _workspace: string;
|
||||
private readonly _adapter: A;
|
||||
private readonly _workspace: string;
|
||||
|
||||
// Maximum cache Block 8192, ttl 30 minutes
|
||||
readonly _blockCaches: LRUCache<string, BaseBlock<B, C>>;
|
||||
readonly _blockIndexer: BlockIndexer<A, B, C>;
|
||||
private readonly _blockCaches: LRUCache<string, BaseBlock<B, C>>;
|
||||
private readonly _blockIndexer: BlockIndexer<A, B, C>;
|
||||
|
||||
readonly _exporters: {
|
||||
private readonly _exporters: {
|
||||
readonly content: BlockExporters<string>;
|
||||
readonly metadata: BlockExporters<
|
||||
Array<[string, number | string | string[]]>
|
||||
@ -84,12 +84,12 @@ export class BlockClient<
|
||||
readonly tag: BlockExporters<string[]>;
|
||||
};
|
||||
|
||||
readonly _eventBus: BlockEventBus;
|
||||
private readonly _eventBus: BlockEventBus;
|
||||
|
||||
readonly _parentMapping: Map<string, string[]>;
|
||||
readonly _pageMapping: Map<string, string>;
|
||||
private readonly _parentMapping: Map<string, string[]>;
|
||||
private readonly _pageMapping: Map<string, string>;
|
||||
|
||||
readonly _root: { node?: BaseBlock<B, C> };
|
||||
private readonly _root: { node?: BaseBlock<B, C> };
|
||||
|
||||
private constructor(
|
||||
adapter: A,
|
||||
|
@ -7,9 +7,9 @@ declare const JWT_DEV: boolean;
|
||||
const logger = getLogger('BlockDB:event_bus');
|
||||
|
||||
export class BlockEventBus {
|
||||
readonly _eventBus: EventTarget;
|
||||
readonly _eventCallbackCache: Map<string, any>;
|
||||
readonly _scopedCache: Map<string, BlockScopedEventBus<any>>;
|
||||
private readonly _eventBus: EventTarget;
|
||||
private readonly _eventCallbackCache: Map<string, any>;
|
||||
private readonly _scopedCache: Map<string, BlockScopedEventBus<any>>;
|
||||
|
||||
constructor(event_bus?: EventTarget) {
|
||||
this._eventBus = event_bus || new EventTarget();
|
||||
@ -68,7 +68,7 @@ type ListenerOptions = {
|
||||
};
|
||||
|
||||
class BlockScopedEventBus<T> extends BlockEventBus {
|
||||
readonly _topic: string;
|
||||
private readonly _topic: string;
|
||||
|
||||
constructor(topic: string, event_bus?: EventTarget) {
|
||||
super(event_bus);
|
||||
|
Loading…
Reference in New Issue
Block a user