Merge remote-tracking branch 'origin/master' into develop

This commit is contained in:
SaikaSakura 2022-08-05 19:27:27 +08:00
commit 812af254aa
39 changed files with 501 additions and 271 deletions

View File

@ -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/> <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) ![952cd7a5-70fe-48ab-b74f-23981d94d2c5](https://user-images.githubusercontent.com/79301703/182365526-df074c64-cee4-45f6-b8e0-b912f17332c6.gif)
# How to use # 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 # 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) - [How to Use](#how-to-use)
- [Table of contents](#table-of-contents) - [Table of contents](#table-of-contents)
- [Shape your page](#shape-your-page) - [Shape your page](#shape-your-page)
@ -89,7 +91,7 @@ Affine is fully built with web technologies so that consistency and accessibilit
# Documentation # 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 ## 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: That's why we are making AFFiNE. Some of the most important features are:
- Transformable - 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. - 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 - 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. - 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.

View File

@ -33,30 +33,8 @@ export function Page(props: PageProps) {
const { page_id } = useParams(); const { page_id } = useParams();
const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } = const { showSpaceSidebar, fixedDisplay, setSpaceSidebarVisible } =
useShowSpaceSidebar(); useShowSpaceSidebar();
const { user } = useUserAndSpaces();
const dailyNotesFlag = useFlag('BooleanDailyNotes', false); 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 ( return (
<LigoApp> <LigoApp>
<LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}> <LigoLeftContainer style={{ width: fixedDisplay ? '300px' : 0 }}>

View File

@ -25,7 +25,7 @@ const AddCard = ({ group }: { group: KanbanGroup }) => {
const { addCard } = useKanban(); const { addCard } = useKanban();
const handleClick = useCallback(async () => { const handleClick = useCallback(async () => {
await addCard(group); await addCard(group);
}, [addCard]); }, [addCard, group]);
return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>; return <AddCardWrapper onClick={handleClick}>+</AddCardWrapper>;
}; };

View File

@ -1,6 +1,11 @@
import type { KanbanCard } from '@toeverything/components/editor-core'; 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 { styled } from '@toeverything/components/ui';
import { useFlag } from '@toeverything/datasource/feature-flags';
const CardContent = styled('div')({ const CardContent = styled('div')({
margin: '20px', margin: '20px',
@ -58,18 +63,24 @@ export const CardItem = ({
block: KanbanCard['block']; block: KanbanCard['block'];
}) => { }) => {
const { addSubItem } = useKanban(); const { addSubItem } = useKanban();
const { openSubPage } = useRefPage();
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
const onAddItem = async () => { const onAddItem = async () => {
await addSubItem(block); await addSubItem(block);
}; };
const onClickCard = async () => {
showKanbanRefPageFlag && openSubPage(id);
};
return ( return (
<CardContainer> <CardContainer onClick={onClickCard}>
<CardContent> <CardContent>
<RenderBlock blockId={id} /> <RenderBlock blockId={id} />
</CardContent> </CardContent>
<CardActions onClick={onAddItem}> <CardActions onClick={onAddItem}>
<PlusIcon /> <PlusIcon />
<span>Add item</span> <span>Add a sub-block</span>
</CardActions> </CardActions>
</CardContainer> </CardContainer>
); );

View File

@ -143,13 +143,13 @@ export const ImageView: FC<ImageView> = ({ block, editor }) => {
type: 'link', type: 'link',
}); });
}; };
const handle_click = (e: React.MouseEvent<HTMLDivElement>) => { const handle_click = async (e: React.MouseEvent<HTMLDivElement>) => {
//TODO clear active selection //TODO clear active selection
// document.getElementsByTagName('body')[0].click(); // document.getElementsByTagName('body')[0].click();
e.stopPropagation(); e.stopPropagation();
e.nativeEvent.stopPropagation(); e.nativeEvent.stopPropagation();
editor.selectionManager.setSelectedNodesIds([block.id]); await editor.selectionManager.setSelectedNodesIds([block.id]);
editor.selectionManager.activeNodeByNodeId(block.id); await editor.selectionManager.activeNodeByNodeId(block.id, 'end');
}; };
const down_file = () => { const down_file = () => {
if (down_ref) { if (down_ref) {

View File

@ -99,7 +99,7 @@ export const TextView: FC<CreateTextView> = ({
if (!parentBlock) { if (!parentBlock) {
return false; return false;
} }
const preParent = await parentBlock.previousSibling();
if (Protocol.Block.Type.group === parentBlock.type) { if (Protocol.Block.Type.group === parentBlock.type) {
const children = await block.children(); const children = await block.children();
const preNode = await block.physicallyPerviousSibling(); const preNode = await block.physicallyPerviousSibling();
@ -129,34 +129,19 @@ export const TextView: FC<CreateTextView> = ({
'start' 'start'
); );
if (block.blockProvider.isEmpty()) { 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(); 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; return true;
} }
} }

View File

@ -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 { 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 { BlockPreview } from './BlockView';
import { formatUrl } from './format-url'; import { formatUrl } from './format-url';
@ -15,7 +27,18 @@ export interface Props {
} }
const getHost = (url: string) => new URL(url).host; 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')<{ const LinkContainer = styled('div')<{
isSelected: boolean; isSelected: boolean;
}>(({ theme, isSelected }) => { }>(({ 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')<{ const SourceViewContainer = styled('div')<{
isSelected: boolean; isSelected: boolean;
}>(({ theme, isSelected }) => { scene: string;
}>(({ theme, isSelected, scene }) => {
return { return {
..._getLinkStyle(scene),
overflow: 'hidden', overflow: 'hidden',
position: 'relative',
borderRadius: theme.affine.shape.borderRadius, borderRadius: theme.affine.shape.borderRadius,
background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent', background: isSelected ? 'rgba(152, 172, 189, 0.1)' : 'transparent',
padding: '8px', padding: '8px',
@ -52,32 +91,96 @@ const SourceViewContainer = styled('div')<{
height: '100%', height: '100%',
border: '1px solid #EAEEF2', border: '1px solid #EAEEF2',
borderRadius: theme.affine.shape.borderRadius, 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 => { export const SourceView: FC<Props> = props => {
const { link, isSelected, block, editorElement } = props; const { link, isSelected, block, editorElement } = props;
const src = formatUrl(link); const src = formatUrl(link);
const openTabOnBrowser = () => { // let iframeShow = useLazyIframe(src, 3000, iframeContainer);
window.open(link, '_blank'); const [currentView] = useCurrentView();
}; const { type } = currentView;
if (src?.startsWith('http')) { if (src?.startsWith('http')) {
return ( return (
<LinkContainer <div style={{ display: 'flex' }}>
isSelected={isSelected} <SourceViewContainer isSelected={isSelected} scene={type}>
onMouseDown={e => e.preventDefault()} <MouseMaskContainer />
onClick={openTabOnBrowser}
> <LazyIframe
<p>{getHost(src)}</p> src={src}
<p>{src}</p> fallback={LoadingContiner()}
</LinkContainer> ></LazyIframe>
</SourceViewContainer>
</div>
); );
} else if (src?.startsWith('affine')) { } else if (src?.startsWith('affine')) {
return ( return (
<SourceViewContainer <SourceViewContainer
isSelected={isSelected} isSelected={isSelected}
style={{ padding: '0' }} style={{ padding: '0' }}
scene={type}
> >
<BlockPreview <BlockPreview
block={block} block={block}

View File

@ -1,9 +1,8 @@
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import type { BlockEditor, AsyncBlock } from './editor'; import type { BlockEditor, AsyncBlock } from './editor';
import type { Column } from '@toeverything/datasource/db-service';
import { genErrorObj } from '@toeverything/utils'; import { genErrorObj } from '@toeverything/utils';
export const RootContext = createContext<{ const RootContext = createContext<{
editor: BlockEditor; editor: BlockEditor;
// TODO: Temporary fix, dependencies in the new architecture are bottom-up, editors do not need to be passed down from the top // 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; editorElement: () => JSX.Element;
@ -14,6 +13,8 @@ export const RootContext = createContext<{
) as any ) as any
); );
export const EditorProvider = RootContext.Provider;
export const useEditor = () => { export const useEditor = () => {
return useContext(RootContext); return useContext(RootContext);
}; };
@ -22,16 +23,3 @@ export const useEditor = () => {
* @deprecated * @deprecated
*/ */
export const BlockContext = createContext<AsyncBlock>(null as any); export const BlockContext = createContext<AsyncBlock>(null as any);
/**
* Context of column information
*
* @deprecated
*/
export const ColumnsContext = createContext<{
fromId: string;
columns: Column[];
}>({
fromId: '',
columns: [],
});

View File

@ -2,14 +2,14 @@ import type { BlockEditor } from './editor';
import { styled, usePatchNodes } from '@toeverything/components/ui'; import { styled, usePatchNodes } from '@toeverything/components/ui';
import type { FC, PropsWithChildren } from 'react'; import type { FC, PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState, useCallback } from 'react'; import React, { useEffect, useRef, useState, useCallback } from 'react';
import { RootContext } from './contexts'; import { EditorProvider } from './Contexts';
import { SelectionRect, SelectionRef } from './Selection'; import { SelectionRect, SelectionRef } from './Selection';
import { import {
Protocol, Protocol,
services, services,
type ReturnUnobserve, type ReturnUnobserve,
} from '@toeverything/datasource/db-service'; } from '@toeverything/datasource/db-service';
import { addNewGroup } from './recast-block'; import { addNewGroup, appendNewGroup } from './recast-block';
import { useIsOnDrag } from './hooks'; import { useIsOnDrag } from './hooks';
interface RenderRootProps { interface RenderRootProps {
@ -151,7 +151,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
}; };
return ( return (
<RootContext.Provider value={{ editor, editorElement }}> <EditorProvider value={{ editor, editorElement }}>
<Container <Container
isWhiteboard={editor.isWhiteboard} isWhiteboard={editor.isWhiteboard}
ref={ref => { ref={ref => {
@ -183,7 +183,7 @@ export const RenderRoot: FC<PropsWithChildren<RenderRootProps>> = ({
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />} {editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
{patchedNodes} {patchedNodes}
</Container> </Container>
</RootContext.Provider> </EditorProvider>
); );
}; };
@ -199,24 +199,32 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
mouseMoved.current = false; mouseMoved.current = false;
return; 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 // If last block is not a group
// create a group with a empty text // create a group with a empty text
if (lastGroupBlock.type !== 'group') { if (lastRootChildren == null) {
addNewGroup(editor, lastBlock, true); appendNewGroup(editor, rootBlock, true);
return; return;
} }
if (lastGroupBlock.childrenIds.length > 1) { if (
addNewGroup(editor, lastBlock, true); lastRootChildren.type !== Protocol.Block.Type.group ||
lastRootChildren.childrenIds.length > 1
) {
addNewGroup(editor, lastRootChildren, true);
return; return;
} }
// If the **only** block in the group is text and is empty // If the **only** block in the group is text and is empty
// active the text block // active the text block
const theGroupChildBlock = await lastGroupBlock.firstChild(); const theGroupChildBlock = await lastRootChildren.firstChild();
if ( if (
theGroupChildBlock && theGroupChildBlock &&
@ -229,7 +237,7 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
return; return;
} }
// else create a new group // else create a new group
addNewGroup(editor, lastBlock, true); addNewGroup(editor, lastRootChildren, true);
}, },
[editor] [editor]
); );

View File

@ -367,15 +367,6 @@ export class Editor implements Virgo {
return blockList; 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()) { async getLastBlock(rootBlockId = this.getRootBlockId()) {
const rootBlock = await this.getBlockById(rootBlockId); const rootBlock = await this.getBlockById(rootBlockId);
if (!rootBlock) { if (!rootBlock) {

View File

@ -1,3 +1,6 @@
import { noop, Point } from '@toeverything/utils';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useEditor } from './Contexts';
import { import {
AsyncBlock, AsyncBlock,
BlockEditor, BlockEditor,
@ -5,9 +8,6 @@ import {
SelectionInfo, SelectionInfo,
SelectionSettingsMap, SelectionSettingsMap,
} from './editor'; } from './editor';
import { noop, Point } from '@toeverything/utils';
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
import { RootContext } from './contexts';
function useRequestReRender() { function useRequestReRender() {
const [, setUpdateCounter] = useState(0); const [, setUpdateCounter] = useState(0);
@ -56,7 +56,7 @@ function useRequestReRender() {
export const useBlock = (blockId: string) => { export const useBlock = (blockId: string) => {
const [block, setBlock] = useState<AsyncBlock>(); const [block, setBlock] = useState<AsyncBlock>();
const requestReRender = useRequestReRender(); const requestReRender = useRequestReRender();
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
if (!blockId) { if (!blockId) {
return undefined; return undefined;
@ -95,7 +95,7 @@ export const useOnSelect = (
blockId: string, blockId: string,
cb: (isSelect: boolean) => void cb: (isSelect: boolean) => void
) => { ) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb); editor.selectionManager.observe(blockId, SelectEventTypes.onSelect, cb);
return () => { return () => {
@ -117,7 +117,7 @@ export const useOnSelectActive = (
blockId: string, blockId: string,
cb: (position: Point | undefined) => void cb: (position: Point | undefined) => void
) => { ) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.selectionManager.observe(blockId, SelectEventTypes.active, cb); editor.selectionManager.observe(blockId, SelectEventTypes.active, cb);
return () => { return () => {
@ -139,7 +139,7 @@ export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
blockId: string, blockId: string,
cb: (args: SelectionSettingsMap[T]) => void cb: (args: SelectionSettingsMap[T]) => void
) => { ) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.selectionManager.observe( editor.selectionManager.observe(
blockId, blockId,
@ -162,7 +162,7 @@ export const useOnSelectSetSelection = <T extends keyof SelectionSettingsMap>(
* @export * @export
*/ */
export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => { export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.selectionManager.onSelectionChange(cb); editor.selectionManager.onSelectionChange(cb);
return () => { return () => {
@ -177,7 +177,7 @@ export const useOnSelectChange = (cb: (info: SelectionInfo) => void) => {
* @export * @export
*/ */
export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => { export const useOnSelectEnd = (cb: (info: SelectionInfo) => void) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.selectionManager.onSelectEnd(cb); editor.selectionManager.onSelectEnd(cb);
return () => { return () => {
@ -195,7 +195,7 @@ export const useOnSelectStartWith = (
blockId: string, blockId: string,
cb: (args: MouseEvent) => void cb: (args: MouseEvent) => void
) => { ) => {
const { editor } = useContext(RootContext); const { editor } = useEditor();
useEffect(() => { useEffect(() => {
editor.mouseManager.onSelectStartWith(blockId, cb); editor.mouseManager.onSelectStartWith(blockId, cb);
return () => { return () => {

View File

@ -1,4 +1,3 @@
export { ColumnsContext, RootContext } from './contexts';
export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot'; export { RenderRoot, MIN_PAGE_WIDTH } from './RenderRoot';
export * from './render-block'; export * from './render-block';
export * from './hooks'; export * from './hooks';
@ -16,3 +15,5 @@ export * from './kanban/types';
export * from './utils'; export * from './utils';
export * from './editor'; export * from './editor';
export { RefPageProvider, useRefPage } from './ref-page';

View File

@ -57,18 +57,13 @@ const isValueBelongOption = (
option: KanbanGroup option: KanbanGroup
) => { ) => {
switch (propertyValue.type) { switch (propertyValue.type) {
case PropertyType.Select || PropertyType.Status: { case PropertyType.Select:
case PropertyType.Status: {
return propertyValue.value === option.id; return propertyValue.value === option.id;
} }
case PropertyType.MultiSelect: { case PropertyType.MultiSelect: {
return propertyValue.value.some(i => i === option.id); 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: { default: {
console.error(propertyValue, option); console.error(propertyValue, option);
throw new Error('Not support group by type'); throw new Error('Not support group by type');

View File

@ -1,6 +1,6 @@
import { Protocol } from '@toeverything/datasource/db-service'; import { Protocol } from '@toeverything/datasource/db-service';
import { useCallback, useContext, useEffect, useState } from 'react'; import { useCallback, useContext, useEffect, useState } from 'react';
import { useEditor } from '../contexts'; import { useEditor } from '../Contexts';
import { AsyncBlock } from '../editor'; import { AsyncBlock } from '../editor';
import { useRecastView } from '../recast-block'; import { useRecastView } from '../recast-block';
import { useRecastBlock } from '../recast-block/Context'; import { useRecastBlock } from '../recast-block/Context';

View File

@ -2,6 +2,7 @@ import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '../editor'; import { AsyncBlock } from '../editor';
import { ComponentType, createContext, ReactNode, useContext } from 'react'; import { ComponentType, createContext, ReactNode, useContext } from 'react';
import { RecastBlock } from './types'; import { RecastBlock } from './types';
import { RefPageProvider } from '../ref-page';
/** /**
* Determine whether the block supports RecastBlock * Determine whether the block supports RecastBlock
@ -47,7 +48,7 @@ export const RecastBlockProvider = ({
return ( return (
<RecastBlockContext.Provider value={block}> <RecastBlockContext.Provider value={block}>
{children} <RefPageProvider>{children}</RefPageProvider>
</RecastBlockContext.Provider> </RecastBlockContext.Provider>
); );
}; };
@ -60,7 +61,7 @@ export const useRecastBlock = () => {
const recastBlock = useContext(RecastBlockContext); const recastBlock = useContext(RecastBlockContext);
if (!recastBlock) { if (!recastBlock) {
throw new Error( 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; return recastBlock;

View File

@ -49,22 +49,3 @@ const SomeBlock = () => {
return <div>...</div>; 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>
</>
);
};
```

View File

@ -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; const [headGroup, ...restGroups] = groups;
// Add all children to the head group // Add all children to the head group
@ -174,7 +174,7 @@ export const splitGroup = async (
} }
splitGroupProperties( splitGroupProperties(
group as RecastBlock, group as unknown as RecastBlock,
newGroupBlock as unknown as RecastBlock newGroupBlock as unknown as RecastBlock
); );
await group.after(newGroupBlock); await group.after(newGroupBlock);
@ -185,6 +185,22 @@ export const splitGroup = async (
return newGroupBlock; 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 ( export const addNewGroup = async (
editor: BlockEditor, editor: BlockEditor,
previousBlock: AsyncBlock, previousBlock: AsyncBlock,

View File

@ -1,5 +1,5 @@
import { nanoid } from 'nanoid'; import { nanoid } from 'nanoid';
import { useCallback } from 'react'; import { MutableRefObject, useCallback, useEffect, useState } from 'react';
import { useRecastBlock } from './Context'; import { useRecastBlock } from './Context';
import { import {
KanbanView, KanbanView,
@ -50,7 +50,33 @@ export const useCurrentView = () => {
); );
return [currentView, setCurrentView] as const; 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 = () => { export const useRecastView = () => {
const recastBlock = useRecastBlock(); const recastBlock = useRecastBlock();
const recastViews = const recastViews =

View 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 = () => {};

View File

@ -0,0 +1 @@
export { useRefPage, RefPageProvider } from './ModalPage';

View File

@ -1,8 +1,8 @@
import { styled, Theme } from '@toeverything/components/ui'; import { styled } from '@toeverything/components/ui';
import { FC, useContext, useLayoutEffect, useMemo, useRef } from 'react'; import { FC, useLayoutEffect, useMemo, useRef } from 'react';
// import { RenderChildren } from './RenderChildren'; // import { RenderChildren } from './RenderChildren';
import { RootContext } from '../contexts'; import { useEditor } from '../Contexts';
import { useBlock } from '../hooks'; import { useBlock } from '../hooks';
interface RenderBlockProps { interface RenderBlockProps {
@ -14,7 +14,7 @@ export const RenderBlock: FC<RenderBlockProps> = ({
blockId, blockId,
hasContainer = true, hasContainer = true,
}) => { }) => {
const { editor, editorElement } = useContext(RootContext); const { editor, editorElement } = useEditor();
const { block } = useBlock(blockId); const { block } = useBlock(blockId);
const blockRef = useRef<HTMLDivElement>(null); const blockRef = useRef<HTMLDivElement>(null);

View File

@ -242,6 +242,7 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
onKeyUpCapture={handleKeyup} onKeyUpCapture={handleKeyup}
ref={commandMenuContentRef} ref={commandMenuContentRef}
> >
{show ? (
<MuiClickAwayListener onClickAway={handleClickAway}> <MuiClickAwayListener onClickAway={handleClickAway}>
<div> <div>
<CommandMenuContainer <CommandMenuContainer
@ -261,6 +262,9 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
/> />
</div> </div>
</MuiClickAwayListener> </MuiClickAwayListener>
) : (
<></>
)}
</div> </div>
); );
}; };

View File

@ -9,7 +9,7 @@ import {
importWorkspace, importWorkspace,
exportWorkspace, exportWorkspace,
useWorkspaceAndPageId, useWorkspaceAndPageId,
useReadingMode, // useReadingMode,
clearWorkspace, clearWorkspace,
} from './util'; } from './util';
@ -63,20 +63,20 @@ export const useSettings = (): SettingItem[] => {
const { workspaceId, pageId } = useWorkspaceAndPageId(); const { workspaceId, pageId } = useWorkspaceAndPageId();
const navigate = useNavigate(); const navigate = useNavigate();
const settingFlags = useSettingFlags(); const settingFlags = useSettingFlags();
const { toggleReadingMode, readingMode } = useReadingMode(); // const { toggleReadingMode, readingMode } = useReadingMode();
const settings: SettingItem[] = [ const settings: SettingItem[] = [
{ // {
type: 'switch', // type: 'switch',
name: 'Reading Mode', // name: 'Reading Mode',
value: readingMode, // value: readingMode,
onChange: () => { // onChange: () => {
toggleReadingMode(); // toggleReadingMode();
}, // },
}, // },
{ // {
type: 'separator', // type: 'separator',
}, // },
{ {
type: 'button', type: 'button',
name: 'Duplicate Page', name: 'Duplicate Page',

View File

@ -64,30 +64,32 @@ export const Activities = () => {
const [recentPages, setRecentPages] = useState([]); const [recentPages, setRecentPages] = useState([]);
const userId = user?.id; const userId = user?.id;
/* temporarily remove:show recently viewed documents */ /* show recently edit documents */
const fetchRecentPages = useCallback(async () => { const getRecentEditPages = useCallback(async () => {
if (!userId || !currentSpaceId) { if (!userId || !currentSpaceId) {
return; return;
} }
const recent_pages = await services.api.userConfig.getRecentPages(
currentSpaceId, const recentEditPages =
userId (await services.api.userConfig.getRecentEditedPages(
); currentSpaceId
setRecentPages(recent_pages); )) || [];
}, [userId, currentSpaceId]);
setRecentPages(recentEditPages);
}, [currentSpaceId, userId]);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
await fetchRecentPages(); await getRecentEditPages();
})(); })();
}, [fetchRecentPages]); }, [getRecentEditPages]);
useEffect(() => { useEffect(() => {
let unobserve: () => void; let unobserve: () => void;
const observe = async () => { const observe = async () => {
unobserve = await services.api.userConfig.observe( unobserve = await services.api.userConfig.observe(
{ workspace: currentSpaceId }, { workspace: currentSpaceId },
fetchRecentPages getRecentEditPages
); );
}; };
observe(); observe();
@ -95,12 +97,13 @@ export const Activities = () => {
return () => { return () => {
unobserve?.(); unobserve?.();
}; };
}, [currentSpaceId, fetchRecentPages]); }, [currentSpaceId, getRecentEditPages]);
return ( return (
<StyledWrapper> <StyledWrapper>
<List style={{ padding: '0px' }}> <List style={{ padding: '0px' }}>
{recentPages.map(({ id, title, lastOpenTime }) => { {recentPages.map(item => {
const { id, title, updated } = item;
return ( return (
<ListItem className="item" key={id}> <ListItem className="item" key={id}>
<StyledItemContent <StyledItemContent
@ -114,7 +117,7 @@ export const Activities = () => {
/> />
<ListItemText <ListItemText
className="itemRight" className="itemRight"
primary={formatDistanceToNow(lastOpenTime, { primary={formatDistanceToNow(updated, {
includeSeconds: true, includeSeconds: true,
})} })}
/> />

View File

@ -13,6 +13,7 @@ import type {
} from '@mui/material'; } from '@mui/material';
import { import {
Avatar, Avatar,
Backdrop,
Box, Box,
Button, Button,
Checkbox, 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. * @deprecated It is not recommended to use Mui directly, because the design will not refer to Mui's interaction logic.
*/ */
export const MuiRadio = Radio; 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;

View File

@ -3,6 +3,7 @@ import { ServiceBaseClass } from '../base';
import { ObserveCallback, ReturnUnobserve } from '../database'; import { ObserveCallback, ReturnUnobserve } from '../database';
import { PageTree } from './page-tree'; import { PageTree } from './page-tree';
import { PageConfigItem } from './types'; import { PageConfigItem } from './types';
import type { QueryIndexMetadata } from '@toeverything/datasource/jwt';
/** Operate the user configuration at the workspace level */ /** Operate the user configuration at the workspace level */
export class UserConfig extends ServiceBaseClass { export class UserConfig extends ServiceBaseClass {
@ -122,4 +123,20 @@ export class UserConfig extends ServiceBaseClass {
const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace); const workspaceDbBlock = await this.getWorkspaceDbBlock(workspace);
workspaceDbBlock.setDecoration(WORKSPACE_CONFIG, workspaceName); 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;
});
}
} }

View File

@ -2,7 +2,15 @@
## Usage ## 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 ```tsx
import { FeatureFlagsProvider } from '@toeverything/datasource/feature-flags'; 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** **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 - `quitFfcDevMode()` quit dev mode
## Running unit tests ## Running unit tests

View File

@ -8,4 +8,18 @@ export const config: IOption = {
// id: 'the user's unique identifier' // id: 'the user's unique identifier'
// } // }
devModePassword: '-', 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,
// },
// ],
}; };

View File

@ -3,8 +3,8 @@ import { Array as YArray, Map as YMap } from 'yjs';
import { RemoteKvService } from '@toeverything/datasource/remote-kv'; import { RemoteKvService } from '@toeverything/datasource/remote-kv';
export class YjsRemoteBinaries { export class YjsRemoteBinaries {
readonly _binaries: YMap<YArray<ArrayBuffer>>; // binary instance private readonly _binaries: YMap<YArray<ArrayBuffer>>; // binary instance
readonly _remoteStorage?: RemoteKvService; private readonly _remoteStorage?: RemoteKvService;
constructor(binaries: YMap<YArray<ArrayBuffer>>, remote_token?: string) { constructor(binaries: YMap<YArray<ArrayBuffer>>, remote_token?: string) {
this._binaries = binaries; this._binaries = binaries;

View File

@ -32,19 +32,21 @@ type YjsBlockInstanceProps = {
}; };
export class YjsBlockInstance implements BlockInstance<YjsContentOperation> { export class YjsBlockInstance implements BlockInstance<YjsContentOperation> {
readonly _id: string; private readonly _id: string;
readonly _block: YMap<unknown>; private readonly _block: YMap<unknown>;
readonly _binary?: YArray<ArrayBuffer>; private readonly _binary?: YArray<ArrayBuffer>;
readonly _children: YArray<string>; private readonly _children: YArray<string>;
readonly _setBlock: ( private readonly _setBlock: (
id: string, id: string,
block: BlockItem<YjsContentOperation> block: BlockItem<YjsContentOperation>
) => Promise<void>; ) => Promise<void>;
readonly _getUpdated: (id: string) => number | undefined; private readonly _getUpdated: (id: string) => number | undefined;
readonly _getCreator: (id: string) => string | undefined; private readonly _getCreator: (id: string) => string | undefined;
readonly _getBlockInstance: (id: string) => YjsBlockInstance | undefined; private readonly _getBlockInstance: (
readonly _childrenListeners: Map<string, BlockListener>; id: string
readonly _contentListeners: Map<string, BlockListener>; ) => YjsBlockInstance | undefined;
private readonly _childrenListeners: Map<string, BlockListener>;
private readonly _contentListeners: Map<string, BlockListener>;
// eslint-disable-next-line @typescript-eslint/naming-convention // eslint-disable-next-line @typescript-eslint/naming-convention
_childrenMap: Map<string, number>; _childrenMap: Map<string, number>;

View File

@ -1,10 +1,9 @@
import { Map as YMap } from 'yjs'; import { Map as YMap } from 'yjs';
export class GateKeeper { export class GateKeeper {
// eslint-disable-next-line @typescript-eslint/naming-convention private readonly _userId: string;
_userId: string; private readonly _creators: YMap<string>;
_creators: YMap<string>; private readonly _common: YMap<string>;
_common: YMap<string>;
constructor(userId: string, creators: YMap<string>, common: YMap<string>) { constructor(userId: string, creators: YMap<string>, common: YMap<string>) {
this._userId = userId; this._userId = userId;

View File

@ -5,10 +5,10 @@ import { HistoryCallback, HistoryManager } from '../../adapter';
type StackItem = UndoManager['undoStack'][0]; type StackItem = UndoManager['undoStack'][0];
export class YjsHistoryManager implements HistoryManager { export class YjsHistoryManager implements HistoryManager {
readonly _blocks: YMap<any>; private readonly _blocks: YMap<any>;
readonly _historyManager: UndoManager; private readonly _historyManager: UndoManager;
readonly _pushListeners: Map<string, HistoryCallback<any>>; private readonly _pushListeners: Map<string, HistoryCallback<any>>;
readonly _popListeners: Map<string, HistoryCallback<any>>; private readonly _popListeners: Map<string, HistoryCallback<any>>;
constructor(scope: YMap<any>, tracker?: any[]) { constructor(scope: YMap<any>, tracker?: any[]) {
this._blocks = scope; this._blocks = scope;

View File

@ -178,22 +178,22 @@ export type YjsInitOptions = {
}; };
export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> { export class YjsAdapter implements AsyncDatabaseAdapter<YjsContentOperation> {
readonly _provider: YjsProviders; private readonly _provider: YjsProviders;
readonly _doc: Doc; // doc instance private readonly _doc: Doc; // doc instance
readonly _awareness: Awareness; // lightweight state synchronization private readonly _awareness: Awareness; // lightweight state synchronization
readonly _gatekeeper: GateKeeper; // Simple access control private readonly _gatekeeper: GateKeeper; // Simple access control
readonly _history: YjsHistoryManager; private readonly _history: YjsHistoryManager;
// Block Collection // Block Collection
// key is a randomly generated global id // key is a randomly generated global id
readonly _blocks: YMap<YMap<unknown>>; private readonly _blocks: YMap<YMap<unknown>>;
readonly _blockUpdated: YMap<number>; private readonly _blockUpdated: YMap<number>;
// Maximum cache Block 1024, ttl 10 minutes // 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( static async init(
workspace: string, workspace: string,

View File

@ -52,7 +52,7 @@ function auto_set(root: ContentOperation, key: string, data: BaseTypes): void {
} }
export class YjsContentOperation implements ContentOperation { export class YjsContentOperation implements ContentOperation {
readonly _content: YAbstractType<unknown>; private readonly _content: YAbstractType<unknown>;
constructor(content: YAbstractType<any>) { constructor(content: YAbstractType<any>) {
this._content = content; this._content = content;
@ -197,7 +197,7 @@ export class YjsContentOperation implements ContentOperation {
} }
class YjsTextOperation extends YjsContentOperation implements TextOperation { class YjsTextOperation extends YjsContentOperation implements TextOperation {
readonly _textContent: YText; private readonly _textContent: YText;
constructor(content: YText) { constructor(content: YText) {
super(content); super(content);
@ -241,8 +241,8 @@ class YjsArrayOperation<T extends ContentTypes>
extends YjsContentOperation extends YjsContentOperation
implements ArrayOperation<T> implements ArrayOperation<T>
{ {
readonly _arrayContent: YArray<T>; private readonly _arrayContent: YArray<T>;
readonly _listeners: Map<string, BlockListener>; private readonly _listeners: Map<string, BlockListener>;
constructor(content: YArray<T>) { constructor(content: YArray<T>) {
super(content); super(content);
@ -344,8 +344,8 @@ class YjsMapOperation<T extends ContentTypes>
extends YjsContentOperation extends YjsContentOperation
implements MapOperation<T> implements MapOperation<T>
{ {
readonly _mapContent: YMap<T>; private readonly _mapContent: YMap<T>;
readonly _listeners: Map<string, BlockListener>; private readonly _listeners: Map<string, BlockListener>;
constructor(content: YMap<T>) { constructor(content: YMap<T>) {
super(content); super(content);

View File

@ -26,11 +26,11 @@ export class AbstractBlock<
B extends BlockInstance<C>, B extends BlockInstance<C>,
C extends ContentOperation C extends ContentOperation
> { > {
readonly _id: string; private readonly _id: string;
readonly #block: BlockInstance<C>; readonly #block: BlockInstance<C>;
readonly _history: HistoryManager; private readonly _history: HistoryManager;
readonly _root?: AbstractBlock<B, C>; private readonly _root?: AbstractBlock<B, C>;
readonly _parentListener: Map<string, BlockListener>; private readonly _parentListener: Map<string, BlockListener>;
_parent?: AbstractBlock<B, C>; _parent?: AbstractBlock<B, C>;

View File

@ -51,19 +51,19 @@ export class BaseBlock<
B extends BlockInstance<C>, B extends BlockInstance<C>,
C extends ContentOperation C extends ContentOperation
> extends AbstractBlock<B, C> { > extends AbstractBlock<B, C> {
readonly _exporters?: Exporters; private readonly _exporters?: Exporters;
readonly _contentExportersGetter: () => Map< private readonly _contentExportersGetter: () => Map<
string, string,
ReadableContentExporter<string, any> ReadableContentExporter<string, any>
>; >;
readonly _metadataExportersGetter: () => Map< private readonly _metadataExportersGetter: () => Map<
string, string,
ReadableContentExporter< ReadableContentExporter<
Array<[string, number | string | string[]]>, Array<[string, number | string | string[]]>,
any any
> >
>; >;
readonly _tagExportersGetter: () => Map< private readonly _tagExportersGetter: () => Map<
string, string,
ReadableContentExporter<string[], any> ReadableContentExporter<string[], any>
>; >;

View File

@ -107,18 +107,18 @@ export class BlockIndexer<
B extends BlockInstance<C>, B extends BlockInstance<C>,
C extends ContentOperation C extends ContentOperation
> { > {
readonly _adapter: A; private readonly _adapter: A;
readonly _idb: BlockIdbInstance; private readonly _idb: BlockIdbInstance;
readonly _blockIndexer: DocumentIndexer<IndexMetadata>; private readonly _blockIndexer: DocumentIndexer<IndexMetadata>;
readonly _blockMetadata: LRUCache<string, QueryMetadata>; private readonly _blockMetadata: LRUCache<string, QueryMetadata>;
readonly _eventBus: BlockEventBus; private readonly _eventBus: BlockEventBus;
readonly _blockBuilder: ( private readonly _blockBuilder: (
block: BlockInstance<C> block: BlockInstance<C>
) => Promise<BaseBlock<B, C>>; ) => Promise<BaseBlock<B, C>>;
readonly _delayIndex: { documents: Map<string, BaseBlock<B, C>> }; private readonly _delayIndex: { documents: Map<string, BaseBlock<B, C>> };
constructor( constructor(
adapter: A, adapter: A,

View File

@ -69,14 +69,14 @@ export class BlockClient<
B extends BlockInstance<C>, B extends BlockInstance<C>,
C extends ContentOperation C extends ContentOperation
> { > {
readonly _adapter: A; private readonly _adapter: A;
readonly _workspace: string; private readonly _workspace: string;
// Maximum cache Block 8192, ttl 30 minutes // Maximum cache Block 8192, ttl 30 minutes
readonly _blockCaches: LRUCache<string, BaseBlock<B, C>>; private readonly _blockCaches: LRUCache<string, BaseBlock<B, C>>;
readonly _blockIndexer: BlockIndexer<A, B, C>; private readonly _blockIndexer: BlockIndexer<A, B, C>;
readonly _exporters: { private readonly _exporters: {
readonly content: BlockExporters<string>; readonly content: BlockExporters<string>;
readonly metadata: BlockExporters< readonly metadata: BlockExporters<
Array<[string, number | string | string[]]> Array<[string, number | string | string[]]>
@ -84,12 +84,12 @@ export class BlockClient<
readonly tag: BlockExporters<string[]>; readonly tag: BlockExporters<string[]>;
}; };
readonly _eventBus: BlockEventBus; private readonly _eventBus: BlockEventBus;
readonly _parentMapping: Map<string, string[]>; private readonly _parentMapping: Map<string, string[]>;
readonly _pageMapping: Map<string, string>; private readonly _pageMapping: Map<string, string>;
readonly _root: { node?: BaseBlock<B, C> }; private readonly _root: { node?: BaseBlock<B, C> };
private constructor( private constructor(
adapter: A, adapter: A,

View File

@ -7,9 +7,9 @@ declare const JWT_DEV: boolean;
const logger = getLogger('BlockDB:event_bus'); const logger = getLogger('BlockDB:event_bus');
export class BlockEventBus { export class BlockEventBus {
readonly _eventBus: EventTarget; private readonly _eventBus: EventTarget;
readonly _eventCallbackCache: Map<string, any>; private readonly _eventCallbackCache: Map<string, any>;
readonly _scopedCache: Map<string, BlockScopedEventBus<any>>; private readonly _scopedCache: Map<string, BlockScopedEventBus<any>>;
constructor(event_bus?: EventTarget) { constructor(event_bus?: EventTarget) {
this._eventBus = event_bus || new EventTarget(); this._eventBus = event_bus || new EventTarget();
@ -68,7 +68,7 @@ type ListenerOptions = {
}; };
class BlockScopedEventBus<T> extends BlockEventBus { class BlockScopedEventBus<T> extends BlockEventBus {
readonly _topic: string; private readonly _topic: string;
constructor(topic: string, event_bus?: EventTarget) { constructor(topic: string, event_bus?: EventTarget) {
super(event_bus); super(event_bus);