Merge pull request #376 from toeverything/develop

chore: small adjuest
This commit is contained in:
DarkSky
2022-09-06 17:05:08 +08:00
committed by GitHub
42 changed files with 843 additions and 525 deletions
-13
View File
@@ -1,15 +1,9 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { Login } from './account';
import Agenda from './agenda';
import { LigoVirgoRootContainer } from './AppContainer';
import Recent from './recent';
import { RoutePrivate } from './RoutePrivate';
import { RoutePublicAutoLogin } from './RoutePublicAutoLogin';
import Search from './search';
import Settings from './settings';
import Shared from './shared';
import Starred from './starred';
import { PageNotFound } from './status/page-not-found';
import { WorkspaceNotFound } from './status/workspace-not-found';
import { Tools } from './tools';
@@ -25,13 +19,6 @@ export function LigoVirgoRoutes() {
path="/error/workspace"
element={<WorkspaceNotFound />}
/>
<Route path="/agenda/*" element={<Agenda />} />
<Route path="/recent" element={<Recent />} />
<Route path="/search" element={<Search />} />
<Route path="/settings" element={<Settings />} />
<Route path="/shared" element={<Shared />} />
<Route path="/started" element={<Starred />} />
<Route path="/ui" element={<UIPage />} />
<Route
path="/:workspace_id/*"
@@ -1,3 +0,0 @@
export default function Recent() {
return <span>Recent</span>;
}
@@ -1,3 +0,0 @@
export default function Search() {
return <span>Search</span>;
}
@@ -1,3 +0,0 @@
export default function Settings() {
return <span>Settings</span>;
}
@@ -1,3 +0,0 @@
export default function Shared() {
return <span>Shared</span>;
}
@@ -1,3 +0,0 @@
export default function Starred() {
return <span>Starred</span>;
}
@@ -31,6 +31,7 @@ export function WorkspaceHome() {
})
);
}
navigate(`/${workspace_id}/${user_initial_page_id}`);
};
navigate_to_user_initial_page();
@@ -1,12 +1,9 @@
import { Route, Routes, useParams } from 'react-router';
import { useUserAndSpaces } from '@toeverything/datasource/state';
import { Route, Routes, useParams } from 'react-router';
import { WorkspaceRootContainer } from './Container';
import { Page } from './docs';
import { Edgeless } from './Edgeless';
import { WorkspaceHome } from './Home';
import Labels from './labels';
import Pages from './pages';
export function WorkspaceContainer() {
@@ -23,7 +20,6 @@ export function WorkspaceContainer() {
return (
<Routes>
<Route path="/" element={<WorkspaceRootContainer />}>
<Route path="/labels" element={<Labels />} />
<Route path="/pages" element={<Pages />} />
<Route
path="/:page_id/edgeless"
+7 -16
View File
@@ -1,21 +1,12 @@
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Box, Grid, Typography } from '@mui/joy';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import 'github-markdown-css';
import AboutText from './about.mdx';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './Common';
import KeepUpdate from './keeupdate.png';
export const AboutUs = () => {
const matches = useMediaQuery('(max-width: 1024px)');
const navigate = useNavigate();
const { i18n } = useTranslation();
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
import 'github-markdown-css/github-markdown-light.css';
import AboutText from './about.mdx';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './common';
import KeepUpdate from './keeupdate.png';
export const AboutUs = () => {
return (
<>
<AFFiNEHeader />
+1 -1
View File
@@ -12,7 +12,7 @@ import { LogoIcon } from '@toeverything/components/icons';
import { useMediaQuery } from '@mui/material';
import CollaborationImage from './collaboration.png';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './Common';
import { AFFiNEFooter, AFFiNEHeader, AFFiNEImage } from './common';
import { GitHub } from './Icons';
import PageImage from './page.png';
import ShapeImage from './shape.png';
@@ -1,26 +1,17 @@
/* eslint-disable max-lines */
/* eslint-disable @typescript-eslint/naming-convention */
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Box, Button, Grid, Typography } from '@mui/joy';
import Option from '@mui/joy/Option';
import Select from '@mui/joy/Select';
import { styled } from '@mui/joy/styles';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import GitHubIcon from '@mui/icons-material/GitHub';
import RedditIcon from '@mui/icons-material/Reddit';
import TelegramIcon from '@mui/icons-material/Telegram';
import { options } from './i18n';
import { DiscordIcon, GitHub } from './Icons';
// eslint-disable-next-line no-restricted-imports
import LogoImage from './logo.png';
import TwitterIcon from '@mui/icons-material/Twitter';
export const AFFiNEImage = styled('img')({
maxWidth: '100%',
objectFit: 'contain',
});
import { DiscordIcon, GitHub } from '../Icons';
import LogoImage from '../logo.png';
import { AFFiNEImage } from './index';
export const AFFiNEFooter = ({
keepupdate = true,
@@ -199,6 +190,52 @@ export const AFFiNEFooter = ({
</Grid>
</Button>
</Box>
<Box sx={{ display: 'flex', width: '100%' }}>
<Button
variant="plain"
sx={{
display: 'flex',
flexDirection: 'column',
margin: 'auto',
padding: '1em',
minWidth: '6em',
}}
onClick={() =>
window.open('https://twitter.com/AffineOfficial')
}
>
<Grid
xs={12}
sx={{
display: 'flex',
justifyContent: 'center',
}}
>
<TwitterIcon
sx={{ width: '36px', height: '36px' }}
/>
</Grid>
<Grid
xs={12}
sx={{
display: 'flex',
margin: 'auto',
marginTop: '1em',
}}
>
<Typography
sx={{
display: 'flex',
color: '#888',
fontSize: '0.5em',
}}
>
Twitter
</Typography>
</Grid>
</Button>
</Box>
<Box
sx={{
display: 'flex',
@@ -341,86 +378,3 @@ export const AFFiNEFooter = ({
</>
);
};
export const AFFiNEHeader = () => {
const matches = useMediaQuery('(max-width: 1024px)');
const navigate = useNavigate();
const { i18n } = useTranslation();
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
const matchesIPAD = useMediaQuery('(max-width: 768px)');
return (
<Grid
container
spacing={2}
sx={{
maxWidth: '1280px',
margin: 'auto',
}}
>
<Grid xs={6}>
<Button
size="lg"
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
onClick={() => navigate('/')}
>
AFFiNE
</Button>
</Grid>
<Grid xs={6} sx={{ display: 'flex', justifyContent: 'right' }}>
<GitHub flat />
<Button
onClick={() => window.open('https://blog.affine.pro')}
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
size="lg"
>
Blog
</Button>
<Button
onClick={() => navigate('/aboutus')}
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
size="lg"
>
About Us
</Button>
<Select
defaultValue="en"
sx={{ display: matchesIPAD ? 'none' : 'intial' }}
onChange={changeLanguage}
>
{options.map(option => (
<Option key={option.value} value={option.value}>
{option.text}
</Option>
))}
</Select>
</Grid>
</Grid>
);
};
+95
View File
@@ -0,0 +1,95 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { useTranslation } from 'react-i18next';
import { useNavigate } from 'react-router-dom';
import { Button, Grid } from '@mui/joy';
import Option from '@mui/joy/Option';
import Select from '@mui/joy/Select';
// eslint-disable-next-line no-restricted-imports
import { useMediaQuery } from '@mui/material';
import { options } from '../i18n';
import { GitHub } from '../Icons';
export const AFFiNEHeader = () => {
const matches = useMediaQuery('(max-width: 1024px)');
const navigate = useNavigate();
const { i18n } = useTranslation();
const changeLanguage = (event: any) => {
i18n.changeLanguage(event);
};
const matchesIPAD = useMediaQuery('(max-width: 768px)');
return (
<Grid
container
spacing={2}
sx={{
maxWidth: '1280px',
margin: 'auto',
}}
>
<Grid xs={6}>
<Button
size="lg"
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
onClick={() => navigate('/')}
>
AFFiNE
</Button>
</Grid>
<Grid xs={6} sx={{ display: 'flex', justifyContent: 'right' }}>
<GitHub flat />
<Button
onClick={() => window.open('https://blog.affine.pro')}
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
size="lg"
>
Blog
</Button>
<Button
onClick={() => navigate('/aboutus')}
variant="plain"
sx={{
padding: matches ? '0' : '0 0.5em',
':hover': { backgroundColor: 'unset' },
fontSize: '24px',
'@media (max-width: 1024px)': {
fontSize: '16px',
},
}}
size="lg"
>
About Us
</Button>
<Select
defaultValue="en"
sx={{ display: matchesIPAD ? 'none' : 'intial' }}
onChange={changeLanguage}
>
{options.map(option => (
<Option key={option.value} value={option.value}>
{option.text}
</Option>
))}
</Select>
</Grid>
</Grid>
);
};
+10
View File
@@ -0,0 +1,10 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { styled } from '@mui/joy/styles';
export const AFFiNEImage = styled('img')({
maxWidth: '100%',
objectFit: 'contain',
});
export { AFFiNEFooter } from './Footer';
export { AFFiNEHeader } from './Header';
+1
View File
@@ -19,6 +19,7 @@ declare module 'slate' {
}
}
export { default as isUrl } from 'is-url';
export { BlockPreview, StyledBlockPreview } from './block-preview';
export { default as Button } from './button';
export { CollapsibleTitle } from './collapsible-title';
@@ -41,7 +41,12 @@ import { InlineDate, withDate } from './plugins/date';
import { DoubleLinkComponent } from './plugins/DoubleLink';
import { LinkComponent, LinkModal, withLinks, wrapLink } from './plugins/link';
import { InlineRefLink } from './plugins/reflink';
import { Contents, isSelectAll, SlateUtils } from './slate-utils';
import {
Contents,
isSelectAll,
SelectionStartAndEnd,
SlateUtils,
} from './slate-utils';
import {
getCommentsIdsOnTextNode,
getExtraPropertiesFromEditorOutmostNode,
@@ -88,8 +93,12 @@ export interface TextProps {
/** Backspace event */
handleBackSpace?: ({
isCollAndStart,
splitContents,
selection,
}: {
isCollAndStart: boolean;
splitContents: Contents;
selection: SelectionStartAndEnd;
}) => boolean | undefined | Promise<boolean | undefined>;
/** Whether markdown is supported */
supportMarkdown?: boolean;
@@ -464,7 +473,13 @@ export const Text = forwardRef<ExtendedTextUtils, TextProps>((props, ref) => {
if (!isCool) {
hideInlineMenu && hideInlineMenu();
}
preventBindIfNeeded(handleBackSpace)(e, { isCollAndStart });
const selection = utils.current.getSelectionStartAndEnd();
const splitContents = utils.current.getSplitContentsBySelection();
preventBindIfNeeded(handleBackSpace)(e, {
isCollAndStart,
selection,
splitContents,
});
};
const onTab = (e: KeyboardEvent) => {
@@ -6,6 +6,7 @@ import { RenderElementProps } from 'slate-react';
export type DoubleLinkElement = {
type: 'link';
linkType: 'doubleLink';
workspaceId: string;
blockId: string;
children: Descendant[];
@@ -25,15 +26,20 @@ export const DoubleLinkComponent = (props: RenderElementProps) => {
[doubleLinkElement, navigate]
);
const displayValue = doubleLinkElement.children
.map((item: any) => item.text)
.join('');
return (
<span>
<PagesIcon style={{ verticalAlign: 'middle', height: '20px' }} />
<a
{...attributes}
style={{ cursor: 'pointer' }}
href={`/${doubleLinkElement.workspaceId}/${doubleLinkElement.blockId}`}
>
<span onClick={handleClickLinkText}>{children}</span>
<span onClick={handleClickLinkText}>
<a {...attributes} style={{ cursor: 'pointer' }}>
<PagesIcon
style={{ verticalAlign: 'middle', height: '20px' }}
/>
<span>
{children}
{displayValue}
</span>
</a>
</span>
);
@@ -1,44 +1,42 @@
import React, {
useEffect,
useMemo,
useRef,
useState,
useCallback,
KeyboardEvent,
MouseEvent,
memo,
} from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import isUrl from 'is-url';
import style9 from 'style9';
import {
Editor,
Transforms,
Element as SlateElement,
Descendant,
Range as SlateRange,
Node,
} from 'slate';
import { ReactEditor } from 'slate-react';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import EditIcon from '@mui/icons-material/Edit';
import LinkOffIcon from '@mui/icons-material/LinkOff';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import { LinkIcon } from '@toeverything/components/icons';
import {
MuiTooltip as Tooltip,
styled,
muiTooltipClasses,
styled,
type MuiTooltipProps,
} from '@toeverything/components/ui';
import {
getRelativeUrlForInternalPageUrl,
isInternalPageUrl,
} from '@toeverything/utils';
import isUrl from 'is-url';
import React, {
KeyboardEvent,
memo,
MouseEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { createPortal } from 'react-dom';
import { useNavigate } from 'react-router-dom';
import {
Descendant,
Editor,
Element as SlateElement,
Node,
Range as SlateRange,
Transforms,
} from 'slate';
import { ReactEditor } from 'slate-react';
import style9 from 'style9';
import { getRandomString } from '../utils';
import { colors } from '../../colors';
import { LinkIcon } from '@toeverything/components/icons';
export type LinkElement = {
type: 'link';
url: string;
@@ -47,13 +45,20 @@ export type LinkElement = {
};
export const withLinks = (editor: ReactEditor) => {
const { isInline } = editor;
const { isInline, isVoid } = editor;
editor.isInline = element => {
// @ts-ignore
return element.type === 'link' ? true : isInline(element);
};
editor.isVoid = element => {
// @ts-ignore
return element.type === 'link' && element.linkType === 'doubleLink'
? true
: isVoid(element);
};
return editor;
};
@@ -2,6 +2,7 @@
import {
Descendant,
Editor,
Element as SlateElement,
Location,
Node as SlateNode,
Path,
@@ -405,11 +406,11 @@ Editor.before = function (
if (element) {
if (isInlineAndVoid(editor, element)) {
// Inline entities need to be drilled out
// target = Editor.before(editor, target);
target = {
path: [0, path[1] - 1],
offset: 0,
};
target = Editor.before(editor, target);
// target = {
// path: [0, path[1] - 1],
// offset: 0,
// };
} else if (editor.isInline(element) && !editor.isVoid(element)) {
// Inline styles such as hyperlinks need to drill directly into it
const inlineTextLength = element?.children?.[0]?.text?.length;
@@ -503,7 +504,7 @@ Editor.after = function (
return target;
};
type SelectionStartAndEnd = {
export type SelectionStartAndEnd = {
selectionStart: Point;
selectionEnd: Point;
};
@@ -517,6 +518,10 @@ export type Contents = {
content: Descendant[];
isEmpty: boolean;
};
contentSelection: {
content: Descendant[];
isEmpty: boolean;
};
};
class SlateUtils {
@@ -572,6 +577,7 @@ class SlateUtils {
anchor: point1,
focus: point2,
});
if (!fragment.length) {
console.error('Debug information:', point1, point2, fragment);
throw new Error('Failed to get content between!');
@@ -602,7 +608,7 @@ class SlateUtils {
for (let i = 0; i < fragmentChildren.length; i++) {
const child = fragmentChildren[i];
if ('type' in child && child.type === 'link') {
i !== fragmentChildren.length - 1 && textChildren.push(child);
textChildren.push(child);
continue;
}
if (!('text' in child)) {
@@ -638,6 +644,10 @@ class SlateUtils {
content: this.getContentBetween(selectionEnd, end),
isEmpty: Point.equals(end, selectionEnd),
},
contentSelection: {
content: this.getContentBetween(selectionStart, selectionEnd),
isEmpty: false,
},
} as Contents;
}
@@ -680,10 +690,7 @@ class SlateUtils {
}
public getStart() {
return Editor.start(this.editor, {
path: [0, 0],
offset: 0,
});
return Editor.start(this.editor, [0]);
}
public getEnd() {
@@ -713,7 +720,10 @@ class SlateUtils {
if (!this.editor) {
return undefined;
}
const { selectionEnd } = this.getSelectionStartAndEnd();
const selectionEnd = this.getSelectionStartAndEnd()?.selectionEnd;
if (!selectionEnd) {
return undefined;
}
return this.getStringBetween(this.getStart(), selectionEnd);
}
@@ -1252,6 +1262,57 @@ class SlateUtils {
});
}
public wrapLink(url: string, preSelection?: Location) {
if (!ReactEditor.isFocused(this.editor) && preSelection) {
Transforms.select(this.editor, preSelection);
}
if (this.isLinkActive()) {
this.unwrapLink();
}
const { selection } = this.editor;
const isCollapsed = selection && this.isCollapsed();
const link = {
type: 'link',
url: url,
children: isCollapsed ? [{ text: url }] : [],
id: getRandomString('link'),
};
if (isCollapsed) {
Transforms.insertNodes(this.editor, link);
} else {
Transforms.wrapNodes(this.editor, link, { split: true });
Transforms.collapse(this.editor, { edge: 'end' });
}
requestAnimationFrame(() => {
ReactEditor.focus(this.editor);
});
}
public isLinkActive() {
const [link] = Editor.nodes(this.editor, {
match: n =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
// @ts-expect-error
n.type === 'link',
});
return !!link;
}
public unwrapLink() {
Transforms.unwrapNodes(this.editor, {
match: n => {
return (
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
// @ts-expect-error
n.type === 'link'
);
},
});
}
/** todo improve if selection is collapsed */
public getCommentsIdsBySelection() {
const commentedTextNodes = Editor.nodes(this.editor, {
@@ -132,25 +132,31 @@ export const NumberedView = ({ block, editor }: CreateView) => {
// Move children to new block
const children = await block.children();
await block.removeChildren();
const next_node = await editor.createBlock(
const nextBlock = await editor.createBlock(
Protocol.Block.Type.numbered
);
if (!next_node) {
if (!nextBlock) {
throw new Error('Failed to create todo block');
}
await next_node.append(...children);
await next_node.setProperties({
if (editor.getRootBlockId() === block.id) {
// If the block is the root block,
// new block can not append as next sibling,
// all new blocks should be append as children.
await block.insert(0, [nextBlock]);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
}
await block.removeChildren();
await nextBlock.append(...children);
await nextBlock.setProperties({
text: { value: after } as ContentColumnValue,
});
await block.setProperties({
text: { value: before } as ContentColumnValue,
});
await block.after(next_node);
editor.selectionManager.activeNodeByNodeId(next_node.id);
await block.after(nextBlock);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
};
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
@@ -88,13 +88,21 @@ export const TodoView = ({ block, editor }: CreateView) => {
Boolean
) as AsyncBlock[];
const next_node = await editor.createBlock(Protocol.Block.Type.todo);
if (!next_node) {
const nextBlock = await editor.createBlock(Protocol.Block.Type.todo);
if (!nextBlock) {
throw new Error('Failed to create todo block');
}
if (editor.getRootBlockId() === block.id) {
// If the block is the root block,
// new block can not append as next sibling,
// all new blocks should be append as children.
await block.insert(0, [nextBlock]);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
}
await block.removeChildren();
await next_node.append(...children);
await next_node.setProperties({
await nextBlock.append(...children);
await nextBlock.setProperties({
text: { value: after } as ContentColumnValue,
});
@@ -102,9 +110,9 @@ export const TodoView = ({ block, editor }: CreateView) => {
text: { value: before } as ContentColumnValue,
collapsed: { value: false },
});
await block.after(next_node);
await block.after(nextBlock);
editor.selectionManager.activeNodeByNodeId(next_node.id);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
};
@@ -108,7 +108,7 @@ const findLowestCommonAncestor = async (
export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
(props, ref) => {
const { block, editor, ...otherOptions } = props;
const { block, editor, handleEnter, ...otherOptions } = props;
const defaultRef = useRef<ExtendedTextUtils>(null);
// Maybe there is a better way
const textRef =
@@ -446,6 +446,38 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
}
}
};
const onTextEnter: TextProps['handleEnter'] = async props => {
const { splitContents } = props;
if (splitContents) {
const { contentBeforeSelection, contentAfterSelection } =
splitContents;
// after[after.length - 1];
if (!contentBeforeSelection.isEmpty) {
const beforeSelection = contentBeforeSelection.content;
const lastItem: any =
beforeSelection.length > 0
? beforeSelection[beforeSelection.length - 1]
: null;
if (lastItem?.linkType === 'doubleLink') {
contentBeforeSelection.content.push({ text: '' });
}
}
if (!contentAfterSelection.isEmpty) {
const afterSelection = contentAfterSelection.content;
const firstItem: any =
afterSelection.length > 0 ? afterSelection[0] : null;
if (firstItem?.linkType === 'doubleLink') {
contentAfterSelection.content.splice(0, 0, {
text: '',
});
}
}
}
return handleEnter && handleEnter(props);
};
if (!properties || !properties.text) {
return <></>;
}
@@ -467,6 +499,7 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
handleUndo={onUndo}
handleRedo={onRedo}
handleEsc={onKeyboardEsc}
handleEnter={onTextEnter}
{...otherOptions}
/>
);
@@ -5,13 +5,14 @@ import type {
} from '@toeverything/components/common';
import {
BaseRange,
Location,
Node,
Path,
Point,
Selection as SlateSelection,
} from 'slate';
import { Editor } from '../editor';
import { AsyncBlock } from '../block';
import { Editor } from '../editor';
import { SelectBlock } from '../selection';
type TextUtilsFunctions =
@@ -44,6 +45,7 @@ type TextUtilsFunctions =
| 'setSelection'
| 'insertNodes'
| 'getNodeByPath'
| 'wrapLink'
| 'getNodeByRange'
| 'convertLeaf2Html';
@@ -160,7 +162,7 @@ export class BlockHelper {
if (properties.text.value.length === 0) {
return properties;
}
let text_value = properties.text.value;
const text_value = properties.text.value;
const {
text: { value: originTextValue, ...otherTextProperties },
@@ -517,4 +519,13 @@ export class BlockHelper {
console.warn('Could find the block text utils');
return undefined;
}
public wrapLink(blockId: string, url: string, preSelection?: Location) {
const text_utils = this._blockTextUtilsMap[blockId];
if (text_utils) {
return text_utils.wrapLink(url, preSelection);
}
console.warn('Could find the block text utils');
return undefined;
}
}
@@ -13,8 +13,6 @@ import type {
BlockFlavors,
ReturnEditorBlock,
} from '@toeverything/datasource/db-service';
import type { IdList, SelectionInfo, SelectionManager } from './selection';
import { Point } from '@toeverything/utils';
import { Observable } from 'rxjs';
import type { AsyncBlock } from './block';
@@ -23,6 +21,7 @@ import type { BlockCommands } from './commands/block-commands';
import type { DragDropManager } from './drag-drop';
import { MouseManager } from './mouse';
import { ScrollManager } from './scroll';
import type { IdList, SelectionInfo, SelectionManager } from './selection';
// import { BrowserClipboard } from './clipboard/browser-clipboard';
@@ -114,6 +113,7 @@ export interface Virgo {
getGroupBlockByPoint: (point: Point) => Promise<AsyncBlock>;
isEdgeless: boolean;
mouseManager: MouseManager;
getHooks: () => HooksRunner & PluginHooks;
}
export interface Plugin {
@@ -4,7 +4,7 @@ import {
BlockDecoration,
MapOperation,
} from '@toeverything/datasource/jwt';
import type { ComponentType, ReactElement } from 'react';
import { ComponentType, ReactElement } from 'react';
import type { EventData } from '../block';
import { AsyncBlock } from '../block';
import { HTML2BlockResult } from '../clipboard';
@@ -108,7 +108,11 @@ export abstract class BaseView {
// Whether the component is empty
isEmpty(block: AsyncBlock): boolean {
const text = block.getProperty('text');
const result = !text?.value?.[0]?.text;
// const result = !text?.value?.[0]?.text;
const result =
text?.value?.findIndex(
(item: any) => item.text || item.children?.length
) === -1;
// Assert that the text is really empty
if (
@@ -1,9 +1,10 @@
import { genErrorObj } from '@toeverything/utils';
import { createContext, PropsWithChildren, useContext } from 'react';
import type { ComponentType, PropsWithChildren } from 'react';
import { createContext, useContext } from 'react';
import { RenderBlockProps } from './RenderBlock';
type BlockRenderProps = {
blockRender: (args: RenderBlockProps) => JSX.Element;
blockRender: ComponentType<RenderBlockProps>;
};
export const BlockRenderContext = createContext<BlockRenderProps>(
+2 -1
View File
@@ -6,7 +6,7 @@ import {
GroupMenuPlugin,
InlineMenuPlugin,
LeftMenuPlugin,
SelectionGroupPlugin,
LinkMenuPlugin,
} from './menu';
import { FullTextSearchPlugin } from './search';
import { TemplatePlugin } from './template';
@@ -24,4 +24,5 @@ export const plugins: PluginCreator[] = [
// SelectionGroupPlugin,
AddCommentPlugin,
GroupMenuPlugin,
LinkMenuPlugin,
];
@@ -107,14 +107,14 @@ export const DoubleLinkMenu = ({
icon: AddIcon,
},
});
!inAddNewPage &&
items.push({
content: {
id: ADD_NEW_PAGE,
content: 'Add new page in...',
icon: AddIcon,
},
});
// !inAddNewPage &&
// items.push({
// content: {
// id: ADD_NEW_PAGE,
// content: 'Add new page in...',
// icon: AddIcon,
// },
// });
return items;
}, [searchResultBlocks, inAddNewPage]);
@@ -137,7 +137,8 @@ export const DoubleLinkMenu = ({
const resetState = useCallback(
(preNodeId: string, nextNodeId: string) => {
editor.blockHelper.removeDoubleLinkSearchSlash(preNodeId);
preNodeId &&
editor.blockHelper.removeDoubleLinkSearchSlash(preNodeId);
setCurBlockId(nextNodeId);
setSearchText('');
setIsOpen(true);
@@ -184,16 +185,19 @@ export const DoubleLinkMenu = ({
}
}
const { type, anchorNode } = editor.selection.currentSelectInfo;
if (!anchorNode) {
return;
}
if (
!isOpen ||
(type === 'Range' &&
anchorNode &&
anchorNode.id !== curBlockId &&
editor.blockHelper.isSelectionCollapsed(anchorNode.id))
) {
const text = editor.blockHelper.getBlockTextBeforeSelection(
anchorNode.id
);
const text =
editor.blockHelper.getBlockTextBeforeSelection(
anchorNode.id
) || '';
if (text.endsWith('[[')) {
resetState(curBlockId, anchorNode.id);
}
@@ -206,7 +210,7 @@ export const DoubleLinkMenu = ({
}
}
},
[editor, isOpen, curBlockId, hideMenu]
[editor, isOpen, curBlockId, hideMenu, resetState]
);
const handleKeyup = useCallback(
@@ -251,6 +255,20 @@ export const DoubleLinkMenu = ({
};
}, [handleKeyup, handleKeyDown, hooks]);
useEffect(() => {
const showDoubleLink = () => {
const { anchorNode } = editor.selection.currentSelectInfo;
editor.blockHelper.insertNodes(anchorNode.id, [{ text: '[[' }], {
select: true,
});
setTimeout(() => {
resetState('', anchorNode.id);
}, 0);
};
editor.plugins.observe('showDoubleLink', showDoubleLink);
return () => editor.plugins.unobserve('showDoubleLink', showDoubleLink);
}, [editor, resetState]);
const insertDoubleLink = useCallback(
async (pageId: string) => {
editor.blockHelper.setSelectDoubleLinkSearchSlash(curBlockId);
@@ -328,46 +346,48 @@ export const DoubleLinkMenu = ({
...doubleLinkMenuStyle,
}}
>
<MuiClickAwayListener onClickAway={() => hideMenu()}>
<Popper
open={isOpen}
anchorEl={anchorEl}
transition
placement="bottom-start"
>
{({ TransitionProps }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: 'left bottom',
}}
>
<Paper>
{inAddNewPage && (
<NewPageSearchContainer>
<Input
ref={newPageSearchRef}
value={filterText}
onChange={handleFilterChange}
placeholder="Search page to add to..."
/>
</NewPageSearchContainer>
)}
<DoubleLinkMenuContainer
editor={editor}
hooks={hooks}
style={style}
blockId={curBlockId}
onSelected={handleSelected}
onClose={hideMenu}
items={menuItems}
types={menuTypes}
/>
</Paper>
</Grow>
)}
</Popper>
</MuiClickAwayListener>
{isOpen && (
<MuiClickAwayListener onClickAway={() => hideMenu()}>
<Popper
open={isOpen}
anchorEl={anchorEl}
transition
placement="bottom-start"
>
{({ TransitionProps }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: 'left bottom',
}}
>
<Paper>
{inAddNewPage && (
<NewPageSearchContainer>
<Input
ref={newPageSearchRef}
value={filterText}
onChange={handleFilterChange}
placeholder="Search page to add to..."
/>
</NewPageSearchContainer>
)}
<DoubleLinkMenuContainer
editor={editor}
hooks={hooks}
style={style}
blockId={curBlockId}
onSelected={handleSelected}
onClose={hideMenu}
items={menuItems}
types={menuTypes}
/>
</Paper>
</Grow>
)}
</Popper>
</MuiClickAwayListener>
)}
</div>
);
};
@@ -3,7 +3,7 @@ import { BasePlugin } from '../../base-plugin';
import { PluginRenderRoot } from '../../utils';
import { DoubleLinkMenu } from './DoubleLinkMenu';
const PLUGIN_NAME = 'reference-menu';
const PLUGIN_NAME = 'doublelink-menu';
export class DoubleLinkMenuPlugin extends BasePlugin {
private _root?: PluginRenderRoot;
@@ -4,4 +4,5 @@ export { GroupMenuPlugin } from './group-menu';
export { InlineMenuPlugin } from './inline-menu';
export { LeftMenuPlugin } from './left-menu/LeftMenuPlugin';
export { MENU_WIDTH as menuWidth } from './left-menu/menu-config';
export { LinkMenuPlugin } from './link-menu';
export { SelectionGroupPlugin } from './selection-group-menu';
@@ -1,14 +1,10 @@
import React, { useCallback } from 'react';
import style9 from 'style9';
import { Tooltip } from '@toeverything/components/ui';
import { uaHelper } from '@toeverything/utils';
import {
inlineMenuNamesKeys,
MacInlineMenuShortcuts,
WinInlineMenuShortcuts,
} from '../config';
import React, { useCallback } from 'react';
import style9 from 'style9';
import { inlineMenuNamesKeys, WinInlineMenuShortcuts } from '../config';
import type { IconItemType, WithEditorSelectionType } from '../types';
type MenuIconItemProps = IconItemType & WithEditorSelectionType;
export const MenuIconItem = ({
@@ -27,6 +23,7 @@ export const MenuIconItem = ({
editor,
type: nameKey,
anchorNodeId: selectionInfo?.anchorNode?.id,
setShow,
});
}
if ([inlineMenuNamesKeys.comment].includes(nameKey)) {
@@ -1,5 +1,5 @@
import type { SvgIconProps } from '@toeverything/components/ui';
import type { Virgo, SelectionInfo } from '@toeverything/framework/virgo';
import type { SelectionInfo, Virgo } from '@toeverything/framework/virgo';
import { inlineMenuNames, INLINE_MENU_UI_TYPES } from './config';
export type WithEditorSelectionType = {
@@ -14,10 +14,12 @@ export type ClickItemHandler = ({
type,
editor,
anchorNodeId,
setShow,
}: {
type: InlineMenuNamesType;
editor: Virgo;
anchorNodeId: string;
setShow?: React.Dispatch<React.SetStateAction<boolean>>;
}) => void;
export type IconItemType = {
@@ -107,6 +107,7 @@ const common_handler_for_inline_menu: ClickItemHandler = ({
editor,
anchorNodeId,
type,
setShow,
}) => {
switch (type) {
case inlineMenuNamesKeys.text:
@@ -184,6 +185,8 @@ const common_handler_for_inline_menu: ClickItemHandler = ({
editor,
blockId: anchorNodeId,
});
// editor.plugins.emit('showAddLink');
// setShow(false);
break;
case inlineMenuNamesKeys.code:
toggle_text_format({
@@ -438,6 +441,10 @@ const common_handler_for_inline_menu: ClickItemHandler = ({
blockType: Protocol.Block.Type.file,
});
break;
case inlineMenuNamesKeys.backlinks:
editor.plugins.emit('showDoubleLink');
setShow(false);
break;
default: // do nothing
}
};
@@ -0,0 +1,259 @@
import { CommonListItem, isUrl } from '@toeverything/components/common';
import { LinkIcon } from '@toeverything/components/icons';
import {
ListButton,
MuiClickAwayListener,
MuiGrow as Grow,
MuiPaper as Paper,
MuiPopper as Popper,
styled,
} from '@toeverything/components/ui';
import { PluginHooks, Virgo } from '@toeverything/framework/virgo';
import {
ChangeEvent,
KeyboardEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useParams } from 'react-router-dom';
import { QueryBlocks, QueryResult } from '../../search';
import { DoubleLinkMenuContainer } from '../double-link-menu/Container';
const ADD_NEW_SUB_PAGE = 'AddNewSubPage';
const ADD_NEW_PAGE = 'AddNewPage';
export type LinkMenuProps = {
editor: Virgo;
hooks: PluginHooks;
};
type LinkMenuStyle = {
left: number;
top: number;
height: number;
};
const normalizeUrl = (url: string) => {
// eslint-disable-next-line no-restricted-globals
return /^https?/.test(url) ? url : `${location.protocol}//${url}`;
};
export const LinkMenu = ({ editor, hooks }: LinkMenuProps) => {
const { page_id: curPageId } = useParams();
const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState(null);
const dialogRef = useRef<HTMLDivElement>();
const inputEl = useRef<HTMLInputElement>();
const [linkMenuStyle, setLinkMenuStyle] = useState<LinkMenuStyle>({
left: 0,
top: 0,
height: 0,
});
const url = '';
const [curBlockId, setCurBlockId] = useState<string>();
const [searchText, setSearchText] = useState<string>();
const [searchResultBlocks, setSearchResultBlocks] = useState<QueryResult>(
[]
);
const menuTypes = useMemo(() => {
return Object.values(searchResultBlocks)
.map(({ id }) => id)
.concat([ADD_NEW_SUB_PAGE, ADD_NEW_PAGE]);
}, [searchResultBlocks]);
const menuItems: CommonListItem[] = useMemo(() => {
const items: CommonListItem[] = [];
if (searchResultBlocks?.length > 0) {
items.push({
renderCustom: () => {
return <ListButton content={'LINK TO PAGE'} />;
},
});
items.push(
...(searchResultBlocks?.map(
block =>
({
block: {
...block,
content: block.content || 'Untitled',
},
} as CommonListItem)
) || [])
);
}
return items;
}, [searchResultBlocks]);
useEffect(() => {
const text = searchText;
QueryBlocks(editor, text, result => {
result = result.filter(item => item.id !== curPageId);
setSearchResultBlocks(result);
});
}, [editor, searchText, curPageId]);
const hideMenu = useCallback(() => {
setIsOpen(false);
editor.blockHelper.removeDoubleLinkSearchSlash(curBlockId);
editor.scrollManager.unLock();
}, [curBlockId, editor]);
const resetState = useCallback(
(preNodeId: string, nextNodeId: string) => {
setCurBlockId(nextNodeId);
setSearchText('');
setIsOpen(true);
editor.scrollManager.lock();
const clientRect =
editor.selection.currentSelectInfo?.browserSelection
?.getRangeAt(0)
?.getBoundingClientRect();
if (clientRect) {
const rectTop = clientRect.top;
const { top, left } = editor.container.getBoundingClientRect();
setLinkMenuStyle({
top: rectTop - top,
left: clientRect.left - left,
height: clientRect.height,
});
setAnchorEl(dialogRef.current);
}
},
[editor]
);
useEffect(() => {
const showLinkMenu = () => {
const { anchorNode } = editor.selection.currentSelectInfo;
resetState('', anchorNode.id);
};
editor.plugins.observe('showAddLink', showLinkMenu);
return () => editor.plugins.unobserve('showAddLink', showLinkMenu);
}, [editor, resetState]);
const handleSelected = async (id: string) => {};
const handleFilterChange = useCallback(
async (e: ChangeEvent<HTMLInputElement>) => {
const text = e.target.value;
await setSearchText(text);
},
[]
);
const addLinkUrlToText = () => {
const newUrl = inputEl.current.value;
if (newUrl && newUrl !== url && isUrl(normalizeUrl(newUrl))) {
editor.blockHelper.wrapLink(curBlockId, newUrl);
hideMenu();
return;
}
};
const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
addLinkUrlToText();
}
if (e.key === 'Escape') {
hideMenu();
}
};
return (
<div
ref={dialogRef}
style={{
position: 'absolute',
width: '10px',
...linkMenuStyle,
}}
>
{isOpen && (
<MuiClickAwayListener onClickAway={() => hideMenu()}>
<Popper
open={isOpen}
anchorEl={anchorEl}
transition
placement="bottom-start"
>
{({ TransitionProps }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin: 'left bottom',
}}
>
<Paper>
<LinkModalContainer>
<LinkModalContainerIcon>
<LinkIcon
style={{
fontSize: '16px',
marginTop: '2px',
}}
/>
</LinkModalContainerIcon>
<LinkModalContainerInput
onKeyDown={handleKeyDown}
placeholder="Paste link url"
autoComplete="off"
value={searchText}
onChange={handleFilterChange}
ref={inputEl}
/>
</LinkModalContainer>
{menuItems.length > 0 && (
<DoubleLinkMenuContainer
editor={editor}
hooks={hooks}
blockId={curBlockId}
onSelected={handleSelected}
onClose={hideMenu}
items={menuItems}
types={menuTypes}
/>
)}
</Paper>
</Grow>
)}
</Popper>
</MuiClickAwayListener>
)}
</div>
);
};
const LinkModalContainer = styled('div')(({ theme }) => ({
display: 'flex',
borderRadius: '4px',
boxShadow: theme.affine.shadows.shadow1,
backgroundColor: '#fff',
alignItems: 'center',
zIndex: '1',
}));
const LinkModalContainerIcon = styled('div')(({ theme }) => ({
display: 'flex',
width: '16px',
margin: '0 16px 0 4px',
color: '#4C6275',
}));
const LinkModalContainerInput = styled('input')(({ theme }) => ({
flex: '1',
outline: 'none',
border: 'none',
padding: '8px',
fontFamily: 'Helvetica,Arial,"Microsoft Yahei",SimHei,sans-serif',
'::-webkit-input-placeholder': {
color: '#98acbd',
},
color: '#4C6275',
}));
@@ -0,0 +1,33 @@
import { StrictMode } from 'react';
import { BasePlugin } from '../../base-plugin';
import { PluginRenderRoot } from '../../utils';
import { LinkMenu } from './LinkMenu';
const PLUGIN_NAME = 'reference-menu';
export class LinkMenuPlugin extends BasePlugin {
private _root?: PluginRenderRoot;
public static override get pluginName(): string {
return PLUGIN_NAME;
}
protected override _onRender(): void {
this._root = new PluginRenderRoot({
name: PLUGIN_NAME,
render: this.editor.reactRenderRoot.render,
});
this._root.mount();
this._root?.render(
<StrictMode>
<LinkMenu editor={this.editor} hooks={this.hooks} />
</StrictMode>
);
}
public override dispose() {
this._root?.unmount();
super.dispose();
}
}
@@ -0,0 +1 @@
export { LinkMenuPlugin } from './Plugin';
@@ -1,14 +1,12 @@
import { Virgo } from '@toeverything/framework/virgo';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router';
import { Virgo, BlockEditor } from '@toeverything/framework/virgo';
import { debounce } from '@toeverything/utils';
import { MuiBox as Box, styled, BaseButton } from '@toeverything/components/ui';
import { BaseButton, styled } from '@toeverything/components/ui';
import {
services,
type ReturnUnobserve,
TemplateFactory,
TemplateMeta,
type ReturnUnobserve,
} from '@toeverything/datasource/db-service';
const PlaceholderPanelContainer = styled('div')({
@@ -48,7 +46,6 @@ export const PlaceholderPanel = (props: PlaceholderPanelProps) => {
y: 0,
});
const [open, setOpen] = useState(false);
// const navigate = useNavigate();
const ifPageChildrenExist = useCallback(async () => {
const rootId = await editor.getRootBlockId();
@@ -5,7 +5,7 @@ import {
styled,
TransitionsModal,
} from '@toeverything/components/ui';
import { BlockEditor, Virgo } from '@toeverything/framework/virgo';
import { BlockEditor, PluginHooks, Virgo } from '@toeverything/framework/virgo';
import { throttle } from '@toeverything/utils';
import { useCallback, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router';
@@ -31,7 +31,7 @@ const styles = style9.create({
export type QueryResult = Awaited<ReturnType<BlockEditor['search']>>;
const query_blocks = (
const queryBlocksExec = (
editor: Virgo,
search: string,
callback: (result: QueryResult) => void
@@ -39,45 +39,58 @@ const query_blocks = (
(editor as BlockEditor).search(search).then(pages => callback(pages));
};
export const QueryBlocks = throttle(query_blocks, 500);
export const QueryBlocks = throttle(queryBlocksExec, 500);
type SearchProps = {
onClose: () => void;
editor: Virgo;
hooks: PluginHooks;
};
export const Search = (props: SearchProps) => {
const { workspace_id } = useParams();
const { workspace_id: workspaceId } = useParams();
const navigate = useNavigate();
const [open, set_open] = useState(true);
const [search, set_search] = useState('');
const [result, set_result] = useState<QueryResult>([]);
const [open, setOpen] = useState(false);
const [search, setSearch] = useState<string>();
const [result, setResult] = useState<QueryResult>([]);
useEffect(() => {
QueryBlocks(props.editor, search, result => {
set_result(result);
});
search !== undefined &&
QueryBlocks(props.editor, search, result => {
setResult(result);
});
}, [props.editor, search]);
const handle_navigate = useCallback(
(id: string) => navigate(`/${workspace_id}/${id}`),
[navigate, workspace_id]
const handleNavigate = useCallback(
(id: string) => navigate(`/${workspaceId}/${id}`),
[navigate, workspaceId]
);
const handleSearch = useCallback(() => {
setOpen(true);
setSearch('');
}, []);
// useEffect(() => {
// const sub = props.hooks.get(HookType.ON_SEARCH).subscribe(handleSearch);
// return () => {
// sub.unsubscribe();
// };
// }, [props, handleSearch]);
return (
<TransitionsModal
open={open}
onClose={() => {
set_open(false);
props.onClose();
setOpen(false);
}}
>
<Box className={styles('wrapper')}>
<SearchInput
autoFocus
value={search}
onChange={e => set_search(e.target.value)}
onChange={e => setSearch(e.target.value)}
/>
<ResultContainer
sx={{ maxHeight: `${result.length * 28 + 32 + 20}px` }}
@@ -89,10 +102,12 @@ export const Search = (props: SearchProps) => {
<BlockPreview
className={styles('resultItem')}
key={block.id}
block={block}
block={{
...block,
content: block.content || 'Untitled',
}}
onClick={() => {
handle_navigate(block.id);
props.onClose();
handleNavigate(block.id);
}}
/>
))}
@@ -1,59 +1,40 @@
import { StrictMode } from 'react';
import { HookType } from '@toeverything/framework/virgo';
import { BasePlugin } from '../base-plugin';
import { PluginRenderRoot } from '../utils';
import { Search } from './Search';
export class FullTextSearchPlugin extends BasePlugin {
#root?: PluginRenderRoot;
private root?: PluginRenderRoot;
public static override get pluginName(): string {
return 'search';
}
public override init(): void {
this.sub.add(
this.hooks.get(HookType.ON_SEARCH).subscribe(this._handleSearch)
);
}
protected override _onRender(): void {
this.#root = new PluginRenderRoot({
this.root = new PluginRenderRoot({
name: FullTextSearchPlugin.pluginName,
render: this.editor.reactRenderRoot.render,
});
this._renderSearch();
}
private unmount() {
if (this.#root) {
this.editor.setHotKeysScope();
this.#root.unmount();
// this.#root = undefined;
}
this.sub.unsubscribe();
}
private _handleSearch = () => {
this.editor.setHotKeysScope('search');
this.render_search();
};
private render_search() {
if (this.#root) {
this.#root.mount();
this.#root.render(
private _renderSearch() {
if (this.root) {
this.root.mount();
this.root.render(
<StrictMode>
<Search
onClose={() => this.unmount()}
editor={this.editor}
/>
<Search editor={this.editor} hooks={this.hooks} />
</StrictMode>
);
}
}
public renderSearch() {
this.render_search();
this._renderSearch();
}
public override dispose() {
this.root?.unmount();
super.dispose();
}
}
@@ -1,5 +1,3 @@
import { useMemo } from 'react';
import {
LogoIcon,
SearchIcon,
@@ -8,10 +6,11 @@ import {
} from '@toeverything/components/icons';
import { IconButton, styled } from '@toeverything/components/ui';
import {
useCurrentEditors,
useLocalTrigger,
useShowSettingsSidebar,
} from '@toeverything/datasource/state';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { EditorBoardSwitcher } from './EditorBoardSwitcher';
import { fsApiSupported } from './FileSystem';
@@ -33,6 +32,14 @@ export const LayoutHeader = () => {
}
}, [isLocalWorkspace, t]);
const { currentEditors } = useCurrentEditors();
const handleSearch = useCallback(() => {
for (const key in currentEditors || {}) {
currentEditors[key].getHooks().onSearch();
}
}, [currentEditors]);
return (
<StyledContainerForHeaderRoot>
<StyledHeaderRoot>
@@ -49,8 +56,8 @@ export const LayoutHeader = () => {
<IconButton
size="large"
hoverColor={'transparent'}
onClick={handleSearch}
disabled={true}
style={{ cursor: 'not-allowed' }}
>
<SearchIcon />
</IconButton>
@@ -1,170 +0,0 @@
// import type {
// ArrayOperation,
// BlockClientInstance,
// BlockImplInstance,
// MapOperation,
// } from '@toeverything/datasource/jwt';
// import { getDateIsoStringWithTimezone } from '@toeverything/utils';
// export async function getRecentPages(
// db: BlockClientInstance,
// userId: string
// ): Promise<string[]> {
// try {
// const config = await db.getWorkspaceConfig<MapOperation<string>>();
// const recent_pages = config.get('recent_pages');
// if (recent_pages?.get(userId)) {
// return [recent_pages.get(userId) as string];
// }
// } catch (e) {
// console.error(e);
// }
// return [''];
// }
// /** Mark the article corresponding to pageId as the most recently accessed article */
// export async function setRecentPages(
// db: BlockClientInstance,
// userId: string,
// pageId: string
// ): Promise<void> {
// try {
// const config = await db.getWorkspaceConfig<MapOperation<string>>();
// const recent_pages = config.get('recent_pages');
// if (recent_pages && recent_pages.get(userId)) {
// recent_pages.set(userId, pageId);
// } else {
// const map = config.createMap<string>();
// map.set(userId, pageId);
// config.set('recent_pages', map);
// }
// } catch (e) {
// console.error(e);
// }
// }
// export async function setPageTree<TreeItem>(
// db: BlockClientInstance,
// treeData: TreeItem[]
// ): Promise<void> {
// try {
// const config = await db.getWorkspaceConfig();
// const array = config.createArray<TreeItem>();
// array.push((treeData as any[]) || []);
// config.set('page_tree', array);
// } catch (e) {
// console.error(e);
// }
// }
// async function update_tree_items_title<
// TreeItem extends { id: string; title: string; children: TreeItem[] }
// >(
// db: BlockClientInstance,
// items: TreeItem[],
// cache: Record<string, string>
// ): Promise<TreeItem[]> {
// for (const item of items) {
// if (cache[item.id]) {
// item.title = cache[item.id];
// } else {
// const page = await db.get(item.id as 'page');
// item.title =
// page.getDecoration<Array<{ text: string }>>('text')?.[0]
// ?.text ||
// 'Untitled ' +
// getDateIsoStringWithTimezone(page.created)
// .slice(11)
// .replace('T', ' ');
// cache[item.id] = item.title;
// }
// if (item.children.length) {
// item.children = await update_tree_items_title(
// db,
// item.children,
// cache
// );
// }
// }
// return [...items];
// }
// export async function getPageTree<TreeItem>(
// db: BlockClientInstance
// ): Promise<TreeItem[]> {
// try {
// const config = await db.getWorkspaceConfig();
// const page_tree = config.get('page_tree') as ArrayOperation<TreeItem>;
// const page_tree_items = page_tree?.slice();
// if (page_tree && page_tree_items?.length) {
// const pages = await update_tree_items_title(
// db,
// page_tree_items as [],
// {}
// );
// return pages;
// }
// } catch (e) {
// console.error(e);
// }
// return [];
// }
// export async function getWorkspaceConfig(
// db: BlockClientInstance,
// userId: string,
// name: string
// ): Promise<string> {
// try {
// // const config = await db.getWorkspaceConfig();
// // const map = config.get_map<string>(name);
// // if (map && map.get(userId)) {
// // return map.get(userId)!;
// // }
// } catch (e) {
// console.error(e);
// }
// return '';
// }
// export async function setWorkspaceConfig(
// db: BlockClientInstance,
// userId: string,
// name: string,
// value: string
// ): Promise<void> {
// try {
// // const config = await db.getWorkspaceConfig();
// // const map = config.get_map<string>(name);
// // map.set(userId, value === undefined || value === null ? '' : value);
// } catch (e) {
// console.error(e);
// }
// }
// export async function createPage(
// db: BlockClientInstance,
// title?: string,
// content?: string,
// metadata?: Record<string, string>
// ): Promise<BlockImplInstance | undefined> {
// if (db) {
// const page: BlockImplInstance = await db.get('page');
// if (title) {
// page.getContent<string>().set('title', title);
// }
// if (metadata) {
// Object.keys(metadata).forEach(property =>
// page.getContent<string>().set(property, metadata[property])
// );
// }
// return page;
// }
// return undefined;
// }
@@ -1,5 +1,3 @@
export { PageTree } from './PageTree';
export * from './types';
export { usePageTree } from './use-page-tree';
// export { getRecentPages, setRecentPages } from './block-page';
+1 -2
View File
@@ -3,7 +3,6 @@ import * as encoding from 'lib0/encoding';
import * as awarenessProtocol from 'y-protocols/awareness';
import * as syncProtocol from 'y-protocols/sync';
import * as Y from 'yjs';
import { Message } from './handler';
import { readMessage } from './processor';
import { WebsocketProvider } from './provider';
@@ -14,7 +13,7 @@ export const registerBroadcastSubscriber = (
document: Y.Doc
) => {
const channel = provider.broadcastChannel;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const subscriber = (data: ArrayBuffer, origin: any) => {
if (origin !== provider) {
const encoder = readMessage(provider, new Uint8Array(data), false);