Merge pull request #106 from toeverything/feat/editable-kanban-card
Feat/editable kanban card
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { CreateView } from '@toeverything/framework/virgo';
|
|
||||||
import {
|
import {
|
||||||
BlockPendantProvider,
|
BlockPendantProvider,
|
||||||
useOnSelect,
|
useOnSelect,
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { Upload } from '../../components/upload/upload';
|
import { CreateView } from '@toeverything/framework/virgo';
|
||||||
|
import { useState } from 'react';
|
||||||
import { SourceView } from '../../components/source-view';
|
import { SourceView } from '../../components/source-view';
|
||||||
import { LinkContainer } from '../../components/style-container';
|
import { LinkContainer } from '../../components/style-container';
|
||||||
|
import { Upload } from '../../components/upload/upload';
|
||||||
|
|
||||||
const MESSAGES = {
|
const MESSAGES = {
|
||||||
ADD_EMBED_LINK: 'Add embed link',
|
ADD_EMBED_LINK: 'Add embed link',
|
||||||
@@ -38,7 +38,6 @@ export const EmbedLinkView = (props: EmbedLinkView) => {
|
|||||||
{embedLinkUrl ? (
|
{embedLinkUrl ? (
|
||||||
<SourceView
|
<SourceView
|
||||||
block={block}
|
block={block}
|
||||||
editorElement={props.editorElement}
|
|
||||||
isSelected={isSelect}
|
isSelected={isSelect}
|
||||||
viewType="embedLink"
|
viewType="embedLink"
|
||||||
link={embedLinkUrl}
|
link={embedLinkUrl}
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
import type { KanbanCard } from '@toeverything/components/editor-core';
|
|
||||||
import {
|
import {
|
||||||
|
KanbanCard,
|
||||||
RenderBlock,
|
RenderBlock,
|
||||||
|
useEditor,
|
||||||
useKanban,
|
useKanban,
|
||||||
useRefPage,
|
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { PenIcon } from '@toeverything/components/icons';
|
||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
MuiClickAwayListener,
|
||||||
|
styled,
|
||||||
|
} from '@toeverything/components/ui';
|
||||||
import { useFlag } from '@toeverything/datasource/feature-flags';
|
import { useFlag } from '@toeverything/datasource/feature-flags';
|
||||||
|
import { useState, type MouseEvent } from 'react';
|
||||||
|
import { useRefPage } from './RefPage';
|
||||||
|
|
||||||
const CardContent = styled('div')({
|
const CardContent = styled('div')({
|
||||||
margin: '20px',
|
margin: '20px',
|
||||||
@@ -23,6 +30,7 @@ const CardActions = styled('div')({
|
|||||||
fontWeight: '300',
|
fontWeight: '300',
|
||||||
color: '#98ACBD',
|
color: '#98ACBD',
|
||||||
transition: 'all ease-in 0.2s',
|
transition: 'all ease-in 0.2s',
|
||||||
|
zIndex: 1,
|
||||||
|
|
||||||
':hover': {
|
':hover': {
|
||||||
background: '#F5F7F8',
|
background: '#F5F7F8',
|
||||||
@@ -39,11 +47,13 @@ const PlusIcon = styled('div')({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const CardContainer = styled('div')({
|
const CardContainer = styled('div')({
|
||||||
|
position: 'relative',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
backgroundColor: '#fff',
|
backgroundColor: '#fff',
|
||||||
border: '1px solid #E2E7ED',
|
border: '1px solid #E2E7ED',
|
||||||
borderRadius: '5px',
|
borderRadius: '5px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
|
||||||
[CardActions.toString()]: {
|
[CardActions.toString()]: {
|
||||||
opacity: '0',
|
opacity: '0',
|
||||||
@@ -55,6 +65,23 @@ const CardContainer = styled('div')({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Overlay = styled('div')({
|
||||||
|
position: 'absolute',
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
background: 'transparent',
|
||||||
|
|
||||||
|
'& > *': {
|
||||||
|
visibility: 'hidden',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '24px',
|
||||||
|
top: '16px',
|
||||||
|
},
|
||||||
|
'&:hover > *': {
|
||||||
|
visibility: 'visible',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const CardItem = ({
|
export const CardItem = ({
|
||||||
id,
|
id,
|
||||||
block,
|
block,
|
||||||
@@ -64,24 +91,43 @@ export const CardItem = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { addSubItem } = useKanban();
|
const { addSubItem } = useKanban();
|
||||||
const { openSubPage } = useRefPage();
|
const { openSubPage } = useRefPage();
|
||||||
|
const [editable, setEditable] = useState(false);
|
||||||
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
|
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
|
||||||
|
const { editor } = useEditor();
|
||||||
|
|
||||||
const onAddItem = async () => {
|
const onAddItem = async () => {
|
||||||
|
setEditable(true);
|
||||||
await addSubItem(block);
|
await addSubItem(block);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickCard = async () => {
|
const onClickCard = async () => {
|
||||||
showKanbanRefPageFlag && openSubPage(id);
|
openSubPage(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickPen = (e: MouseEvent<Element>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setEditable(true);
|
||||||
|
editor.selectionManager.activeNodeByNodeId(block.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CardContainer onClick={onClickCard}>
|
<MuiClickAwayListener onClickAway={() => setEditable(false)}>
|
||||||
<CardContent>
|
<CardContainer>
|
||||||
<RenderBlock blockId={id} />
|
<CardContent>
|
||||||
</CardContent>
|
<RenderBlock blockId={id} />
|
||||||
<CardActions onClick={onAddItem}>
|
</CardContent>
|
||||||
<PlusIcon />
|
{showKanbanRefPageFlag && !editable && (
|
||||||
<span>Add a sub-block</span>
|
<Overlay onClick={onClickCard}>
|
||||||
</CardActions>
|
<IconButton backgroundColor="#fff" onClick={onClickPen}>
|
||||||
</CardContainer>
|
<PenIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Overlay>
|
||||||
|
)}
|
||||||
|
<CardActions onClick={onAddItem}>
|
||||||
|
<PlusIcon />
|
||||||
|
<span>Add a sub-block</span>
|
||||||
|
</CardActions>
|
||||||
|
</CardContainer>
|
||||||
|
</MuiClickAwayListener>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { useEditor } from '@toeverything/components/editor-core';
|
||||||
import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui';
|
import { MuiBackdrop, styled, useTheme } from '@toeverything/components/ui';
|
||||||
import { createContext, ReactNode, useContext, useState } from 'react';
|
import { createContext, ReactNode, useContext, useState } from 'react';
|
||||||
import { createPortal } from 'react-dom';
|
import { createPortal } from 'react-dom';
|
||||||
import { RenderBlock } from '../render-block';
|
|
||||||
|
|
||||||
const Dialog = styled('div')({
|
const Dialog = styled('div')({
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -30,7 +30,7 @@ const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
|
|||||||
onClick={closeSubPage}
|
onClick={closeSubPage}
|
||||||
>
|
>
|
||||||
<Dialog
|
<Dialog
|
||||||
onClick={e => {
|
onClick={(e: { stopPropagation: () => void }) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -43,9 +43,21 @@ const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ModalPage = ({ blockId }: { blockId: string | null }) => {
|
const ModalPage = ({ blockId }: { blockId: string | null }) => {
|
||||||
|
const { editor, editorElement } = useEditor();
|
||||||
|
|
||||||
|
const AffineEditor = editorElement as any;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={!!blockId}>
|
<Modal open={!!blockId}>
|
||||||
{blockId && <RenderBlock blockId={blockId} />}
|
{blockId && (
|
||||||
|
<AffineEditor
|
||||||
|
workspace={editor.workspace}
|
||||||
|
rootBlockId={blockId}
|
||||||
|
scrollBlank={false}
|
||||||
|
// use edgeless mode prevent padding and blank bottom
|
||||||
|
isEdgeless
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -4,30 +4,33 @@ import { SceneKanbanContext } from './context';
|
|||||||
import { CardContainerWrapper } from './dndable/wrapper/CardContainerWrapper';
|
import { CardContainerWrapper } from './dndable/wrapper/CardContainerWrapper';
|
||||||
import type { ComponentType } from 'react';
|
import type { ComponentType } from 'react';
|
||||||
import type { CreateView } from '@toeverything/framework/virgo';
|
import type { CreateView } from '@toeverything/framework/virgo';
|
||||||
|
import { RefPageProvider } from './RefPage';
|
||||||
|
|
||||||
export const SceneKanban: ComponentType<CreateView> = withKanban<CreateView>(
|
export const SceneKanban: ComponentType<CreateView> = withKanban<CreateView>(
|
||||||
({ editor, block }) => {
|
({ editor, block }) => {
|
||||||
const { kanban } = useKanban();
|
const { kanban } = useKanban();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SceneKanbanContext.Provider value={{ editor, block }}>
|
<RefPageProvider>
|
||||||
<CardContainerWrapper
|
<SceneKanbanContext.Provider value={{ editor, block }}>
|
||||||
dataSource={kanban}
|
<CardContainerWrapper
|
||||||
render={({
|
dataSource={kanban}
|
||||||
activeId,
|
render={({
|
||||||
items,
|
activeId,
|
||||||
containerIds,
|
items,
|
||||||
isSortingContainer,
|
containerIds,
|
||||||
}) => (
|
isSortingContainer,
|
||||||
<CardContainer
|
}) => (
|
||||||
activeId={activeId}
|
<CardContainer
|
||||||
items={items}
|
activeId={activeId}
|
||||||
isSortingContainer={isSortingContainer}
|
items={items}
|
||||||
containerIds={containerIds}
|
isSortingContainer={isSortingContainer}
|
||||||
/>
|
containerIds={containerIds}
|
||||||
)}
|
/>
|
||||||
/>
|
)}
|
||||||
</SceneKanbanContext.Provider>
|
/>
|
||||||
|
</SceneKanbanContext.Provider>
|
||||||
|
</RefPageProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { memo, useEffect, useRef, useState } from 'react';
|
|
||||||
import { nanoid } from 'nanoid';
|
import { nanoid } from 'nanoid';
|
||||||
|
import { memo, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { StyledBlockPreview } from '@toeverything/components/common';
|
import { StyledBlockPreview } from '@toeverything/components/common';
|
||||||
|
import { AsyncBlock, useEditor } from '@toeverything/components/editor-core';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { AsyncBlock } from '@toeverything/framework/virgo';
|
|
||||||
import { debounce, sleep } from '@toeverything/utils';
|
import { debounce, sleep } from '@toeverything/utils';
|
||||||
|
|
||||||
const updateTitle = async (
|
const updateTitle = async (
|
||||||
@@ -73,15 +73,15 @@ const useBlockTitle = (block: AsyncBlock, blockId: string) => {
|
|||||||
type BlockPreviewProps = {
|
type BlockPreviewProps = {
|
||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
blockId: string;
|
blockId: string;
|
||||||
editorElement?: () => JSX.Element;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const InternalBlockPreview = (props: BlockPreviewProps) => {
|
const InternalBlockPreview = (props: BlockPreviewProps) => {
|
||||||
const container = useRef<HTMLDivElement>();
|
const container = useRef<HTMLDivElement>();
|
||||||
const [preview, setPreview] = useState(true);
|
const [preview, setPreview] = useState(true);
|
||||||
const title = useBlockTitle(props.block, props.blockId);
|
const title = useBlockTitle(props.block, props.blockId);
|
||||||
|
const { editorElement } = useEditor();
|
||||||
|
|
||||||
const AffineEditor = props.editorElement as any;
|
const AffineEditor = editorElement as any;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (container?.current) {
|
if (container?.current) {
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
AsyncBlock,
|
AsyncBlock,
|
||||||
useCurrentView,
|
useCurrentView,
|
||||||
useLazyIframe,
|
|
||||||
} from '@toeverything/components/editor-core';
|
} from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import { ReactElement, ReactNode, useEffect, useRef, useState } from 'react';
|
import { ReactNode, useEffect, useRef, useState } from 'react';
|
||||||
import { SCENE_CONFIG } from '../../blocks/group/config';
|
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';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
editorElement?: () => JSX.Element;
|
|
||||||
viewType?: string;
|
viewType?: string;
|
||||||
link: string;
|
link: string;
|
||||||
// onResizeEnd: (data: any) => void;
|
// onResizeEnd: (data: any) => void;
|
||||||
@@ -150,7 +148,7 @@ const LoadingContiner = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SourceView = (props: Props) => {
|
export const SourceView = (props: Props) => {
|
||||||
const { link, isSelected, block, editorElement } = props;
|
const { link, isSelected, block } = props;
|
||||||
const src = formatUrl(link);
|
const src = formatUrl(link);
|
||||||
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
// let iframeShow = useLazyIframe(src, 3000, iframeContainer);
|
||||||
const [currentView] = useCurrentView();
|
const [currentView] = useCurrentView();
|
||||||
@@ -161,10 +159,7 @@ export const SourceView = (props: Props) => {
|
|||||||
<SourceViewContainer isSelected={isSelected} scene={type}>
|
<SourceViewContainer isSelected={isSelected} scene={type}>
|
||||||
<MouseMaskContainer />
|
<MouseMaskContainer />
|
||||||
|
|
||||||
<LazyIframe
|
<LazyIframe src={src} fallback={LoadingContiner()} />
|
||||||
src={src}
|
|
||||||
fallback={LoadingContiner()}
|
|
||||||
></LazyIframe>
|
|
||||||
</SourceViewContainer>
|
</SourceViewContainer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -175,11 +170,7 @@ export const SourceView = (props: Props) => {
|
|||||||
style={{ padding: '0' }}
|
style={{ padding: '0' }}
|
||||||
scene={type}
|
scene={type}
|
||||||
>
|
>
|
||||||
<BlockPreview
|
<BlockPreview block={block} blockId={src} />
|
||||||
block={block}
|
|
||||||
editorElement={editorElement}
|
|
||||||
blockId={src}
|
|
||||||
/>
|
|
||||||
</SourceViewContainer>
|
</SourceViewContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import { SelectBlock } from '../selection';
|
|||||||
export interface CreateView {
|
export interface CreateView {
|
||||||
block: AsyncBlock;
|
block: AsyncBlock;
|
||||||
editor: Editor;
|
editor: Editor;
|
||||||
editorElement: () => JSX.Element;
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use recast table instead
|
* @deprecated Use recast table instead
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ export * from './utils';
|
|||||||
|
|
||||||
export * from './editor';
|
export * from './editor';
|
||||||
|
|
||||||
export { RefPageProvider, useRefPage } from './ref-page';
|
export { useEditor } from './Contexts';
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ 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
|
||||||
@@ -48,7 +47,7 @@ export const RecastBlockProvider = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<RecastBlockContext.Provider value={block}>
|
<RecastBlockContext.Provider value={block}>
|
||||||
<RefPageProvider>{children}</RefPageProvider>
|
{children}
|
||||||
</RecastBlockContext.Provider>
|
</RecastBlockContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { useRefPage, RefPageProvider } from './ModalPage';
|
|
||||||
@@ -13,7 +13,7 @@ export function RenderBlock({
|
|||||||
blockId,
|
blockId,
|
||||||
hasContainer = true,
|
hasContainer = true,
|
||||||
}: RenderBlockProps) {
|
}: RenderBlockProps) {
|
||||||
const { editor, editorElement } = useEditor();
|
const { editor } = useEditor();
|
||||||
const { block } = useBlock(blockId);
|
const { block } = useBlock(blockId);
|
||||||
|
|
||||||
const setRef = useCallback(
|
const setRef = useCallback(
|
||||||
@@ -50,7 +50,6 @@ export function RenderBlock({
|
|||||||
block={block}
|
block={block}
|
||||||
columns={columns.columns}
|
columns={columns.columns}
|
||||||
columnsFromId={columns.fromId}
|
columnsFromId={columns.fromId}
|
||||||
editorElement={editorElement}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const plugins: PluginCreator[] = [
|
|||||||
CommandMenuPlugin,
|
CommandMenuPlugin,
|
||||||
ReferenceMenuPlugin,
|
ReferenceMenuPlugin,
|
||||||
TemplatePlugin,
|
TemplatePlugin,
|
||||||
SelectionGroupPlugin,
|
// SelectionGroupPlugin,
|
||||||
AddCommentPlugin,
|
AddCommentPlugin,
|
||||||
GroupMenuPlugin,
|
GroupMenuPlugin,
|
||||||
];
|
];
|
||||||
|
|||||||
Reference in New Issue
Block a user