fix: lint
This commit is contained in:
@@ -5,7 +5,10 @@ import { getSession } from '@toeverything/components/board-sessions';
|
|||||||
import { deepCopy, TldrawApp } from '@toeverything/components/board-state';
|
import { deepCopy, TldrawApp } from '@toeverything/components/board-state';
|
||||||
import { tools } from '@toeverything/components/board-tools';
|
import { tools } from '@toeverything/components/board-tools';
|
||||||
import { TDShapeType } from '@toeverything/components/board-types';
|
import { TDShapeType } from '@toeverything/components/board-types';
|
||||||
import { RecastBlockProvider } from '@toeverything/components/editor-core';
|
import {
|
||||||
|
getClipDataOfBlocksById,
|
||||||
|
RecastBlockProvider,
|
||||||
|
} from '@toeverything/components/editor-core';
|
||||||
import { services } from '@toeverything/datasource/db-service';
|
import { services } from '@toeverything/datasource/db-service';
|
||||||
import { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo';
|
import { AsyncBlock, BlockEditor } from '@toeverything/framework/virgo';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
@@ -16,7 +19,11 @@ interface AffineBoardProps {
|
|||||||
rootBlockId: string;
|
rootBlockId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AffineBoard = ({ workspace, rootBlockId }: AffineBoardProps) => {
|
const AffineBoard = ({
|
||||||
|
workspace,
|
||||||
|
rootBlockId,
|
||||||
|
editor,
|
||||||
|
}: AffineBoardProps & { editor: BlockEditor }) => {
|
||||||
const [app, set_app] = useState<TldrawApp>();
|
const [app, set_app] = useState<TldrawApp>();
|
||||||
const [document] = useState(() => {
|
const [document] = useState(() => {
|
||||||
return {
|
return {
|
||||||
@@ -61,8 +68,20 @@ const AffineBoard = ({ workspace, rootBlockId }: AffineBoardProps) => {
|
|||||||
onMount(app) {
|
onMount(app) {
|
||||||
set_app(app);
|
set_app(app);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async onCopy(e, groupIds) {
|
||||||
|
const clip = await getClipDataOfBlocksById(
|
||||||
|
editor,
|
||||||
|
groupIds
|
||||||
|
);
|
||||||
|
|
||||||
|
e.clipboardData?.setData(
|
||||||
|
clip.getMimeType(),
|
||||||
|
clip.getData()
|
||||||
|
);
|
||||||
|
},
|
||||||
async onChangePage(app, shapes, bindings, assets) {
|
async onChangePage(app, shapes, bindings, assets) {
|
||||||
await Promise.all(
|
Promise.all(
|
||||||
Object.entries(shapes).map(async ([id, shape]) => {
|
Object.entries(shapes).map(async ([id, shape]) => {
|
||||||
if (shape === undefined) {
|
if (shape === undefined) {
|
||||||
return services.api.editorBlock.delete({
|
return services.api.editorBlock.delete({
|
||||||
@@ -167,7 +186,11 @@ export const AffineBoardWitchContext = ({
|
|||||||
}, [editor, rootBlockId]);
|
}, [editor, rootBlockId]);
|
||||||
return page ? (
|
return page ? (
|
||||||
<RecastBlockProvider block={page}>
|
<RecastBlockProvider block={page}>
|
||||||
<AffineBoard workspace={workspace} rootBlockId={rootBlockId} />
|
<AffineBoard
|
||||||
|
workspace={workspace}
|
||||||
|
rootBlockId={rootBlockId}
|
||||||
|
editor={editor}
|
||||||
|
/>
|
||||||
</RecastBlockProvider>
|
</RecastBlockProvider>
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
TDShapeType,
|
TDShapeType,
|
||||||
TransformInfo,
|
TransformInfo,
|
||||||
} from '@toeverything/components/board-types';
|
} from '@toeverything/components/board-types';
|
||||||
|
import type { BlockEditor } from '@toeverything/components/editor-core';
|
||||||
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
|
||||||
import { styled } from '@toeverything/components/ui';
|
import { styled } from '@toeverything/components/ui';
|
||||||
import type { SyntheticEvent } from 'react';
|
import type { MouseEvent, SyntheticEvent } from 'react';
|
||||||
import { memo, useCallback, useEffect, useRef } from 'react';
|
import { memo, useCallback, useEffect, useRef } from 'react';
|
||||||
import {
|
import {
|
||||||
defaultTextStyle,
|
defaultTextStyle,
|
||||||
@@ -66,6 +67,7 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
Component = TDShapeUtil.Component<T, E, TDMeta>(
|
||||||
({ shape, meta: { app }, events, isEditing, onShapeChange }, ref) => {
|
({ shape, meta: { app }, events, isEditing, onShapeChange }, ref) => {
|
||||||
const containerRef = useRef<HTMLDivElement>();
|
const containerRef = useRef<HTMLDivElement>();
|
||||||
|
const editorRef = useRef<BlockEditor>();
|
||||||
const {
|
const {
|
||||||
workspace,
|
workspace,
|
||||||
rootBlockId,
|
rootBlockId,
|
||||||
@@ -135,6 +137,27 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
}
|
}
|
||||||
}, [app, state, shape.id, editingText, editingId]);
|
}, [app, state, shape.id, editingText, editingId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
if (isEditing) {
|
||||||
|
const lastBlock =
|
||||||
|
await editorRef.current.getLastBlock();
|
||||||
|
editorRef.current.selectionManager.activeNodeByNodeId(
|
||||||
|
lastBlock.id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, [isEditing]);
|
||||||
|
|
||||||
|
const onMouseDown = useCallback(
|
||||||
|
(e: MouseEvent) => {
|
||||||
|
if (e.detail === 2) {
|
||||||
|
app.setEditingText(shape.id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[app, shape.id]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HTMLContainer ref={ref} {...events}>
|
<HTMLContainer ref={ref} {...events}>
|
||||||
<Container
|
<Container
|
||||||
@@ -143,12 +166,14 @@ export class EditorUtil extends TDShapeUtil<T, E> {
|
|||||||
onPointerDown={stopPropagation}
|
onPointerDown={stopPropagation}
|
||||||
onMouseEnter={activateIfEditing}
|
onMouseEnter={activateIfEditing}
|
||||||
onDragEnter={activateIfEditing}
|
onDragEnter={activateIfEditing}
|
||||||
|
onMouseDown={onMouseDown}
|
||||||
>
|
>
|
||||||
<MemoAffineEditor
|
<MemoAffineEditor
|
||||||
workspace={workspace}
|
workspace={workspace}
|
||||||
rootBlockId={rootBlockId}
|
rootBlockId={rootBlockId}
|
||||||
scrollBlank={false}
|
scrollBlank={false}
|
||||||
isEdgeless
|
isEdgeless
|
||||||
|
ref={editorRef}
|
||||||
/>
|
/>
|
||||||
{editingText ? null : <Mask />}
|
{editingText ? null : <Mask />}
|
||||||
</Container>
|
</Container>
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ interface TDCallbacks {
|
|||||||
* (optional) A callback to run when the user exports their page or selection.
|
* (optional) A callback to run when the user exports their page or selection.
|
||||||
*/
|
*/
|
||||||
onExport?: (app: TldrawApp, info: TDExport) => Promise<void>;
|
onExport?: (app: TldrawApp, info: TDExport) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* (optional) A callback to run when the shape is copied.
|
||||||
|
*/
|
||||||
|
onCopy?: (e: ClipboardEvent, ids: string[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TldrawAppCtorProps {
|
export interface TldrawAppCtorProps {
|
||||||
@@ -1898,12 +1902,14 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
/**
|
/**
|
||||||
* Copy one or more shapes to the clipboard.
|
* Copy one or more shapes to the clipboard.
|
||||||
* @param ids The ids of the shapes to copy.
|
* @param ids The ids of the shapes to copy.
|
||||||
|
* @param pageId
|
||||||
|
* @param e
|
||||||
*/
|
*/
|
||||||
copy = (
|
copy = async (
|
||||||
ids = this.selectedIds,
|
ids = this.selectedIds,
|
||||||
pageId = this.currentPageId,
|
pageId = this.currentPageId,
|
||||||
e?: ClipboardEvent
|
e?: ClipboardEvent
|
||||||
): this => {
|
) => {
|
||||||
e?.preventDefault();
|
e?.preventDefault();
|
||||||
|
|
||||||
this.clipboard = this.get_clipboard(ids, pageId);
|
this.clipboard = this.get_clipboard(ids, pageId);
|
||||||
@@ -1919,17 +1925,24 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
if (e) {
|
if (e) {
|
||||||
e.clipboardData?.setData('text/html', tldrawString);
|
e.clipboardData?.setData('text/html', tldrawString);
|
||||||
|
await this.callbacks.onCopy?.(e, this.selectedIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigator.clipboard && window.ClipboardItem) {
|
/**
|
||||||
navigator.clipboard.write([
|
* Reasons for not using Clipboard API for now:
|
||||||
new ClipboardItem({
|
* 1. The `clipboardData.setData` method temporarily satisfies the need for replication functionality
|
||||||
'text/html': new Blob([tldrawString], {
|
* 2. Clipboard API requires the user to agree to access(https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
|
||||||
type: 'text/html',
|
*
|
||||||
}),
|
* **/
|
||||||
}),
|
// if (navigator.clipboard && window.ClipboardItem) {
|
||||||
]);
|
// navigator.clipboard.write([
|
||||||
}
|
// new ClipboardItem({
|
||||||
|
// 'text/html': new Blob([tldrawString], {
|
||||||
|
// type: 'text/html',
|
||||||
|
// }),
|
||||||
|
// }),
|
||||||
|
// ]);
|
||||||
|
// }
|
||||||
|
|
||||||
this.pasteInfo.offset = [0, 0];
|
this.pasteInfo.offset = [0, 0];
|
||||||
this.pasteInfo.center = [0, 0];
|
this.pasteInfo.center = [0, 0];
|
||||||
@@ -3841,7 +3854,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
|
|
||||||
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
|
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
|
||||||
if (typeof svgStr === 'string') {
|
if (typeof svgStr === 'string') {
|
||||||
let viewBox = new DOMParser().parseFromString(svgStr, 'text/xml');
|
const viewBox = new DOMParser().parseFromString(svgStr, 'text/xml');
|
||||||
return viewBox.children[0].getAttribute('viewBox');
|
return viewBox.children[0].getAttribute('viewBox');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4125,7 +4138,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onPointerDown: TLPointerEventHandler = (info, e) => {
|
onPointerDown: TLPointerEventHandler = (info, e) => {
|
||||||
if (e.buttons === 4) {
|
if (e.button === 1) {
|
||||||
this.patchState({
|
this.patchState({
|
||||||
settings: {
|
settings: {
|
||||||
forcePanning: true,
|
forcePanning: true,
|
||||||
@@ -4142,6 +4155,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onPointerUp: TLPointerEventHandler = (info, e) => {
|
onPointerUp: TLPointerEventHandler = (info, e) => {
|
||||||
|
if (e.button === 1) {
|
||||||
|
this.patchState({
|
||||||
|
settings: {
|
||||||
|
forcePanning: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
this.isPointing = false;
|
this.isPointing = false;
|
||||||
this.updateInputs(info, e);
|
this.updateInputs(info, e);
|
||||||
this.currentTool.onPointerUp?.(info, e);
|
this.currentTool.onPointerUp?.(info, e);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { HooksRunner } from '../types';
|
import { HooksRunner } from '../types';
|
||||||
import {
|
|
||||||
OFFICE_CLIPBOARD_MIMETYPE,
|
|
||||||
InnerClipInfo,
|
|
||||||
ClipBlockInfo,
|
|
||||||
} from './types';
|
|
||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
import { AsyncBlock } from '../block';
|
|
||||||
import ClipboardParse from './clipboard-parse';
|
import ClipboardParse from './clipboard-parse';
|
||||||
import { SelectInfo } from '../selection';
|
|
||||||
import {
|
|
||||||
Protocol,
|
|
||||||
BlockFlavorKeys,
|
|
||||||
services,
|
|
||||||
} from '@toeverything/datasource/db-service';
|
|
||||||
import { MarkdownParser } from './markdown-parse';
|
import { MarkdownParser } from './markdown-parse';
|
||||||
import { shouldHandlerContinue } from './utils';
|
import { shouldHandlerContinue } from './utils';
|
||||||
import { Paste } from './paste';
|
import { Paste } from './paste';
|
||||||
|
|||||||
@@ -1,23 +1,16 @@
|
|||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
import { SelectionManager, SelectInfo, SelectBlock } from '../selection';
|
import { SelectionManager } from '../selection';
|
||||||
import { HookType, PluginHooks } from '../types';
|
import { HookType, PluginHooks } from '../types';
|
||||||
import {
|
|
||||||
ClipBlockInfo,
|
|
||||||
OFFICE_CLIPBOARD_MIMETYPE,
|
|
||||||
InnerClipInfo,
|
|
||||||
} from './types';
|
|
||||||
import { Clip } from './clip';
|
|
||||||
import assert from 'assert';
|
|
||||||
import ClipboardParse from './clipboard-parse';
|
import ClipboardParse from './clipboard-parse';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
|
import { Copy } from './copy';
|
||||||
class ClipboardPopulator {
|
class ClipboardPopulator {
|
||||||
private _editor: Editor;
|
private _editor: Editor;
|
||||||
private _hooks: PluginHooks;
|
private _hooks: PluginHooks;
|
||||||
private _selectionManager: SelectionManager;
|
private _selectionManager: SelectionManager;
|
||||||
private _clipboardParse: ClipboardParse;
|
private _clipboardParse: ClipboardParse;
|
||||||
private _sub = new Subscription();
|
private _sub = new Subscription();
|
||||||
|
private _copy: Copy;
|
||||||
constructor(
|
constructor(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
hooks: PluginHooks,
|
hooks: PluginHooks,
|
||||||
@@ -27,128 +20,15 @@ class ClipboardPopulator {
|
|||||||
this._hooks = hooks;
|
this._hooks = hooks;
|
||||||
this._selectionManager = selectionManager;
|
this._selectionManager = selectionManager;
|
||||||
this._clipboardParse = new ClipboardParse(editor);
|
this._clipboardParse = new ClipboardParse(editor);
|
||||||
|
this._copy = new Copy(editor);
|
||||||
this._sub.add(
|
this._sub.add(
|
||||||
hooks
|
hooks.get(HookType.BEFORE_COPY).subscribe(this._copy.handleCopy)
|
||||||
.get(HookType.BEFORE_COPY)
|
|
||||||
.subscribe(this._populateAppClipboard)
|
|
||||||
);
|
);
|
||||||
this._sub.add(
|
this._sub.add(
|
||||||
hooks.get(HookType.BEFORE_CUT).subscribe(this._populateAppClipboard)
|
hooks.get(HookType.BEFORE_CUT).subscribe(this._copy.handleCopy)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _populateAppClipboard = async (e: ClipboardEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
const clips = await this.getClips();
|
|
||||||
if (!clips.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: is not compatible with safari
|
|
||||||
const success = this._copyToClipboardFromPc(clips);
|
|
||||||
if (!success) {
|
|
||||||
// This way, not compatible with firefox
|
|
||||||
const clipboardData = e.clipboardData;
|
|
||||||
if (clipboardData) {
|
|
||||||
try {
|
|
||||||
clips.forEach(clip => {
|
|
||||||
clipboardData.setData(
|
|
||||||
clip.getMimeType(),
|
|
||||||
clip.getData()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// TODO handle exception
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private _copyToClipboardFromPc(clips: any[]) {
|
|
||||||
let success = false;
|
|
||||||
const tempElem = document.createElement('textarea');
|
|
||||||
tempElem.value = 'temp';
|
|
||||||
document.body.appendChild(tempElem);
|
|
||||||
tempElem.select();
|
|
||||||
tempElem.setSelectionRange(0, tempElem.value.length);
|
|
||||||
|
|
||||||
const listener = function (e: any) {
|
|
||||||
const clipboardData = e.clipboardData;
|
|
||||||
if (clipboardData) {
|
|
||||||
clips.forEach(clip => {
|
|
||||||
clipboardData.setData(clip.getMimeType(), clip.getData());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
tempElem.removeEventListener('copy', listener);
|
|
||||||
} as any;
|
|
||||||
|
|
||||||
tempElem.addEventListener('copy', listener);
|
|
||||||
try {
|
|
||||||
success = document.execCommand('copy');
|
|
||||||
} finally {
|
|
||||||
tempElem.removeEventListener('copy', listener);
|
|
||||||
document.body.removeChild(tempElem);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getClipBlockInfo(selBlock: SelectBlock) {
|
|
||||||
const block = await this._editor.getBlockById(selBlock.blockId);
|
|
||||||
const blockView = this._editor.getView(block.type);
|
|
||||||
assert(blockView);
|
|
||||||
const blockInfo: ClipBlockInfo = {
|
|
||||||
type: block.type,
|
|
||||||
properties: blockView.getSelProperties(block, selBlock),
|
|
||||||
children: [] as any[],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < selBlock.children.length; i++) {
|
|
||||||
const childInfo = await this._getClipBlockInfo(
|
|
||||||
selBlock.children[i]
|
|
||||||
);
|
|
||||||
blockInfo.children.push(childInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return blockInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _getInnerClip(): Promise<InnerClipInfo> {
|
|
||||||
const clips: ClipBlockInfo[] = [];
|
|
||||||
const selectInfo: SelectInfo =
|
|
||||||
await this._selectionManager.getSelectInfo();
|
|
||||||
for (let i = 0; i < selectInfo.blocks.length; i++) {
|
|
||||||
const selBlock = selectInfo.blocks[i];
|
|
||||||
const clipBlockInfo = await this._getClipBlockInfo(selBlock);
|
|
||||||
clips.push(clipBlockInfo);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
select: selectInfo,
|
|
||||||
data: clips,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getClips() {
|
|
||||||
const clips: any[] = [];
|
|
||||||
|
|
||||||
const innerClip = await this._getInnerClip();
|
|
||||||
clips.push(
|
|
||||||
new Clip(
|
|
||||||
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED,
|
|
||||||
JSON.stringify(innerClip)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const htmlClip = await this._clipboardParse.generateHtml();
|
|
||||||
htmlClip &&
|
|
||||||
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
|
||||||
|
|
||||||
return clips;
|
|
||||||
}
|
|
||||||
|
|
||||||
disposeInternal() {
|
disposeInternal() {
|
||||||
this._sub.unsubscribe();
|
this._sub.unsubscribe();
|
||||||
this._hooks = null;
|
this._hooks = null;
|
||||||
|
|||||||
102
libs/components/editor-core/src/editor/clipboard/copy.ts
Normal file
102
libs/components/editor-core/src/editor/clipboard/copy.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { Editor } from '../editor';
|
||||||
|
import { SelectInfo } from '../selection';
|
||||||
|
import { OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||||
|
import { Clip } from './clip';
|
||||||
|
import ClipboardParse from './clipboard-parse';
|
||||||
|
import { getClipDataOfBlocksById } from './utils';
|
||||||
|
import { copyToClipboard } from '@toeverything/utils';
|
||||||
|
class Copy {
|
||||||
|
private _editor: Editor;
|
||||||
|
private _clipboardParse: ClipboardParse;
|
||||||
|
|
||||||
|
constructor(editor: Editor) {
|
||||||
|
this._editor = editor;
|
||||||
|
this._clipboardParse = new ClipboardParse(editor);
|
||||||
|
|
||||||
|
this.handleCopy = this.handleCopy.bind(this);
|
||||||
|
}
|
||||||
|
public async handleCopy(e: ClipboardEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
const clips = await this.getClips();
|
||||||
|
if (!clips.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// TODO: is not compatible with safari
|
||||||
|
const success = this._copyToClipboardFromPc(clips);
|
||||||
|
if (!success) {
|
||||||
|
// This way, not compatible with firefox
|
||||||
|
const clipboardData = e.clipboardData;
|
||||||
|
if (clipboardData) {
|
||||||
|
try {
|
||||||
|
clips.forEach(clip => {
|
||||||
|
clipboardData.setData(
|
||||||
|
clip.getMimeType(),
|
||||||
|
clip.getData()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// TODO handle exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getClips() {
|
||||||
|
const clips: Clip[] = [];
|
||||||
|
|
||||||
|
// get custom clip
|
||||||
|
const affineClip = await this._getAffineClip();
|
||||||
|
clips.push(affineClip);
|
||||||
|
|
||||||
|
// get common html clip
|
||||||
|
const htmlClip = await this._clipboardParse.generateHtml();
|
||||||
|
htmlClip &&
|
||||||
|
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, htmlClip));
|
||||||
|
|
||||||
|
return clips;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _getAffineClip(): Promise<Clip> {
|
||||||
|
const selectInfo: SelectInfo =
|
||||||
|
await this._editor.selectionManager.getSelectInfo();
|
||||||
|
|
||||||
|
return getClipDataOfBlocksById(
|
||||||
|
this._editor,
|
||||||
|
selectInfo.blocks.map(block => block.blockId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _copyToClipboardFromPc(clips: any[]) {
|
||||||
|
let success = false;
|
||||||
|
const tempElem = document.createElement('textarea');
|
||||||
|
tempElem.value = 'temp';
|
||||||
|
document.body.appendChild(tempElem);
|
||||||
|
tempElem.select();
|
||||||
|
tempElem.setSelectionRange(0, tempElem.value.length);
|
||||||
|
|
||||||
|
const listener = function (e: any) {
|
||||||
|
const clipboardData = e.clipboardData;
|
||||||
|
if (clipboardData) {
|
||||||
|
clips.forEach(clip => {
|
||||||
|
clipboardData.setData(clip.getMimeType(), clip.getData());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
tempElem.removeEventListener('copy', listener);
|
||||||
|
} as any;
|
||||||
|
|
||||||
|
tempElem.addEventListener('copy', listener);
|
||||||
|
try {
|
||||||
|
success = document.execCommand('copy');
|
||||||
|
} finally {
|
||||||
|
tempElem.removeEventListener('copy', listener);
|
||||||
|
document.body.removeChild(tempElem);
|
||||||
|
}
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Copy };
|
||||||
@@ -241,15 +241,12 @@ export class Paste {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
selectedBlock.setProperties({
|
await selectedBlock.setProperties({
|
||||||
text: {
|
text: {
|
||||||
value: newTextValue,
|
value: newTextValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const pasteBlocks = await this._createBlocks(blocks);
|
const pastedBlocks = await this._createBlocks(blocks);
|
||||||
await Promise.all(
|
|
||||||
pasteBlocks.map(block => selectedBlock.after(block))
|
|
||||||
);
|
|
||||||
|
|
||||||
const nextBlock = await this._editor.createBlock(
|
const nextBlock = await this._editor.createBlock(
|
||||||
selectedBlock?.type
|
selectedBlock?.type
|
||||||
@@ -259,11 +256,12 @@ export class Paste {
|
|||||||
value: nextTextValue,
|
value: nextTextValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
pasteBlocks[pasteBlocks.length - 1].after(nextBlock);
|
|
||||||
|
|
||||||
this._setEndSelectToBlock(
|
await this._insertBlocksAfterBlock(selectedBlock, [
|
||||||
pasteBlocks[pasteBlocks.length - 1].id
|
...pastedBlocks,
|
||||||
);
|
nextBlock,
|
||||||
|
]);
|
||||||
|
await this._setEndSelectToBlock(nextBlock.id);
|
||||||
} else {
|
} else {
|
||||||
this._editor.blockHelper.insertNodes(
|
this._editor.blockHelper.insertNodes(
|
||||||
selectedBlock.id,
|
selectedBlock.id,
|
||||||
@@ -327,10 +325,7 @@ export class Paste {
|
|||||||
value: newTextValue,
|
value: newTextValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const pasteBlocks = await this._createBlocks(blocks);
|
const pastedBlocks = await this._createBlocks(blocks);
|
||||||
pasteBlocks.forEach((block: AsyncBlock) => {
|
|
||||||
selectedBlock.after(block);
|
|
||||||
});
|
|
||||||
const nextBlock = await this._editor.createBlock(
|
const nextBlock = await this._editor.createBlock(
|
||||||
selectedBlock?.type
|
selectedBlock?.type
|
||||||
);
|
);
|
||||||
@@ -339,11 +334,12 @@ export class Paste {
|
|||||||
value: nextTextValue,
|
value: nextTextValue,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
pasteBlocks[pasteBlocks.length - 1].after(nextBlock);
|
await this._insertBlocksAfterBlock(selectedBlock, [
|
||||||
|
...pastedBlocks,
|
||||||
|
nextBlock,
|
||||||
|
]);
|
||||||
|
|
||||||
this._setEndSelectToBlock(
|
await this._setEndSelectToBlock(nextBlock.id);
|
||||||
pasteBlocks[pasteBlocks.length - 1].id
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this._editor.blockHelper.insertNodes(
|
this._editor.blockHelper.insertNodes(
|
||||||
selectedBlock.id,
|
selectedBlock.id,
|
||||||
@@ -353,10 +349,10 @@ export class Paste {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const pasteBlocks = await this._createBlocks(blocks);
|
const pastedBlocks = await this._createBlocks(blocks);
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pasteBlocks.map(block => selectedBlock.after(block))
|
pastedBlocks.map(block => selectedBlock.after(block))
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isSelectedBlockEmpty) {
|
if (isSelectedBlockEmpty) {
|
||||||
@@ -364,7 +360,7 @@ export class Paste {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._setEndSelectToBlock(
|
this._setEndSelectToBlock(
|
||||||
pasteBlocks[pasteBlocks.length - 1].id
|
pastedBlocks[pastedBlocks.length - 1].id
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,27 +370,39 @@ export class Paste {
|
|||||||
currentSelectInfo.blocks[currentSelectInfo.blocks.length - 1]
|
currentSelectInfo.blocks[currentSelectInfo.blocks.length - 1]
|
||||||
.blockId
|
.blockId
|
||||||
);
|
);
|
||||||
const pasteBlocks = await this._createBlocks(blocks);
|
const pastedBlocks = await this._createBlocks(blocks);
|
||||||
|
|
||||||
let groupBlock: AsyncBlock;
|
let groupBlock: AsyncBlock;
|
||||||
if (
|
if (selectedBlock?.type === 'page') {
|
||||||
selectedBlock?.type === 'group' ||
|
|
||||||
selectedBlock?.type === 'page'
|
|
||||||
) {
|
|
||||||
groupBlock = await this._editor.createBlock('group');
|
groupBlock = await this._editor.createBlock('group');
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pasteBlocks.map(block => groupBlock.append(block))
|
pastedBlocks.map(block => groupBlock.append(block))
|
||||||
);
|
);
|
||||||
await selectedBlock.after(groupBlock);
|
await selectedBlock.after(groupBlock);
|
||||||
|
} else if (selectedBlock?.type === 'group') {
|
||||||
|
await Promise.all(
|
||||||
|
pastedBlocks.map(block => selectedBlock.append(block))
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pasteBlocks.map(block => selectedBlock.after(block))
|
pastedBlocks.map(block => selectedBlock.after(block))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this._setEndSelectToBlock(pasteBlocks[pasteBlocks.length - 1].id);
|
this._setEndSelectToBlock(pastedBlocks[pastedBlocks.length - 1].id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async _insertBlocksAfterBlock(
|
||||||
|
targetBlock: AsyncBlock,
|
||||||
|
blocks: AsyncBlock[]
|
||||||
|
) {
|
||||||
|
if (blocks.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const [firstBlock, ...otherBlock] = blocks;
|
||||||
|
await targetBlock.after(firstBlock);
|
||||||
|
await this._insertBlocksAfterBlock(blocks[0], otherBlock);
|
||||||
|
}
|
||||||
private async _setEndSelectToBlock(blockId: string) {
|
private async _setEndSelectToBlock(blockId: string) {
|
||||||
const block = await this._editor.getBlockById(blockId);
|
const block = await this._editor.getBlockById(blockId);
|
||||||
const isBlockCanEdit = Paste._isTextEditBlock(block.type);
|
const isBlockCanEdit = Paste._isTextEditBlock(block.type);
|
||||||
@@ -406,14 +414,37 @@ export class Paste {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _createBlocks(blocks: ClipBlockInfo[], parentId?: string) {
|
private _flatGroupBlocks(blocks: ClipBlockInfo[]) {
|
||||||
|
return blocks.reduce(
|
||||||
|
(blockList: ClipBlockInfo[], block: ClipBlockInfo) => {
|
||||||
|
if (block.type === 'group') {
|
||||||
|
block?.children?.forEach(childBlock => {
|
||||||
|
childBlock.children = this._flatGroupBlocks(
|
||||||
|
childBlock.children
|
||||||
|
);
|
||||||
|
});
|
||||||
|
block?.children?.length &&
|
||||||
|
blockList.push(...block.children);
|
||||||
|
} else {
|
||||||
|
blockList.push(block);
|
||||||
|
block.children = this._flatGroupBlocks(block.children);
|
||||||
|
}
|
||||||
|
return blockList;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private async _createBlocks(blocks: ClipBlockInfo[]) {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
blocks.map(async clipBlockInfo => {
|
this._flatGroupBlocks(blocks).map(async clipBlockInfo => {
|
||||||
const block = await this._editor.createBlock(
|
const block = await this._editor.createBlock(
|
||||||
clipBlockInfo.type
|
clipBlockInfo.type
|
||||||
);
|
);
|
||||||
block?.setProperties(clipBlockInfo.properties);
|
block?.setProperties(clipBlockInfo.properties);
|
||||||
await this._createBlocks(clipBlockInfo.children, block?.id);
|
const children = await this._createBlocks(
|
||||||
|
clipBlockInfo.children
|
||||||
|
);
|
||||||
|
await Promise.all(children.map(child => block?.append(child)));
|
||||||
return block;
|
return block;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Editor } from '../editor';
|
import { Editor } from '../editor';
|
||||||
|
import { ClipBlockInfo, OFFICE_CLIPBOARD_MIMETYPE } from './types';
|
||||||
|
import { Clip } from './clip';
|
||||||
|
|
||||||
export const shouldHandlerContinue = (event: Event, editor: Editor) => {
|
export const shouldHandlerContinue = (event: Event, editor: Editor) => {
|
||||||
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
const filterNodes = ['INPUT', 'SELECT', 'TEXTAREA'];
|
||||||
@@ -12,3 +14,39 @@ export const shouldHandlerContinue = (event: Event, editor: Editor) => {
|
|||||||
|
|
||||||
return editor.selectionManager.currentSelectInfo.type !== 'None';
|
return editor.selectionManager.currentSelectInfo.type !== 'None';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getClipInfoOfBlockById = async (
|
||||||
|
editor: Editor,
|
||||||
|
blockId: string
|
||||||
|
) => {
|
||||||
|
const block = await editor.getBlockById(blockId);
|
||||||
|
const blockView = editor.getView(block.type);
|
||||||
|
const blockInfo: ClipBlockInfo = {
|
||||||
|
type: block.type,
|
||||||
|
properties: blockView.getSelProperties(block, {}),
|
||||||
|
children: [] as ClipBlockInfo[],
|
||||||
|
};
|
||||||
|
const children = (await block?.children()) ?? [];
|
||||||
|
|
||||||
|
for (let i = 0; i < children.length; i++) {
|
||||||
|
const childInfo = await getClipInfoOfBlockById(editor, children[i].id);
|
||||||
|
blockInfo.children.push(childInfo);
|
||||||
|
}
|
||||||
|
return blockInfo;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getClipDataOfBlocksById = async (
|
||||||
|
editor: Editor,
|
||||||
|
blockIds: string[]
|
||||||
|
) => {
|
||||||
|
const clipInfos = await Promise.all(
|
||||||
|
blockIds.map(blockId => getClipInfoOfBlockById(editor, blockId))
|
||||||
|
);
|
||||||
|
|
||||||
|
return new Clip(
|
||||||
|
OFFICE_CLIPBOARD_MIMETYPE.DOCS_DOCUMENT_SLICE_CLIP_WRAPPED,
|
||||||
|
JSON.stringify({
|
||||||
|
data: clipInfos,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
/* eslint-disable max-lines */
|
/* eslint-disable max-lines */
|
||||||
import HotKeys from 'hotkeys-js';
|
import HotKeys from 'hotkeys-js';
|
||||||
import LRUCache from 'lru-cache';
|
|
||||||
|
|
||||||
import type { PatchNode } from '@toeverything/components/ui';
|
import type { PatchNode } from '@toeverything/components/ui';
|
||||||
import type {
|
import type {
|
||||||
@@ -46,10 +45,7 @@ export interface EditorCtorProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Editor implements Virgo {
|
export class Editor implements Virgo {
|
||||||
private _cacheManager = new LRUCache<string, Promise<AsyncBlock | null>>({
|
private _cacheManager = new Map<string, Promise<AsyncBlock | null>>();
|
||||||
max: 8192,
|
|
||||||
ttl: 1000 * 60 * 30,
|
|
||||||
});
|
|
||||||
public mouseManager = new MouseManager(this);
|
public mouseManager = new MouseManager(this);
|
||||||
public selectionManager = new SelectionManager(this);
|
public selectionManager = new SelectionManager(this);
|
||||||
public keyboardManager = new KeyboardManager(this);
|
public keyboardManager = new KeyboardManager(this);
|
||||||
@@ -234,7 +230,12 @@ export class Editor implements Virgo {
|
|||||||
return await services.api.editorBlock.update(patches);
|
return await services.api.editorBlock.update(patches);
|
||||||
},
|
},
|
||||||
remove: async ({ workspace, id }: WorkspaceAndBlockId) => {
|
remove: async ({ workspace, id }: WorkspaceAndBlockId) => {
|
||||||
return await services.api.editorBlock.delete({ workspace, id });
|
const ret = await services.api.editorBlock.delete({
|
||||||
|
workspace,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
this._cacheManager.delete(id);
|
||||||
|
return ret;
|
||||||
},
|
},
|
||||||
observe: async (
|
observe: async (
|
||||||
{ workspace, id }: WorkspaceAndBlockId,
|
{ workspace, id }: WorkspaceAndBlockId,
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ export { BlockDropPlacement, HookType, GroupDirection } from './types';
|
|||||||
export type { Plugin, PluginCreator, PluginHooks, Virgo } from './types';
|
export type { Plugin, PluginCreator, PluginHooks, Virgo } from './types';
|
||||||
export { BaseView, getTextHtml, getTextProperties } from './views/base-view';
|
export { BaseView, getTextHtml, getTextProperties } from './views/base-view';
|
||||||
export type { ChildrenView, CreateView } from './views/base-view';
|
export type { ChildrenView, CreateView } from './views/base-view';
|
||||||
|
export { getClipDataOfBlocksById } from './clipboard/utils';
|
||||||
|
|||||||
@@ -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,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
{
|
|
||||||
"executors": {
|
|
||||||
"svgOptimize": {
|
|
||||||
"implementation": "./svgo.js",
|
|
||||||
"schema": "./schema.json",
|
|
||||||
"description": "Run `svgo` (to optimize svg)."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"executors": "./executor.json"
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/schema",
|
|
||||||
"type": "object",
|
|
||||||
"cli": "nx",
|
|
||||||
"properties": {
|
|
||||||
"svgOptimize": {
|
|
||||||
"type": "string",
|
|
||||||
"description": "optimize svg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
const path = require('path');
|
|
||||||
const svgo = require('svgo');
|
|
||||||
const { readdir, readFile, writeFile, exists } = require('fs/promises');
|
|
||||||
const { pascalCase, paramCase } = require('change-case');
|
|
||||||
const svgr = require('@svgr/core');
|
|
||||||
|
|
||||||
async function optimizeSvg(folder) {
|
|
||||||
try {
|
|
||||||
const icons = await readdir(folder);
|
|
||||||
const generateIcons = icons
|
|
||||||
.filter(n => n.endsWith('.svg'))
|
|
||||||
.map(async icon => {
|
|
||||||
let originSvg;
|
|
||||||
try {
|
|
||||||
originSvg = await readFile(path.resolve(folder, icon));
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
let optimizedSvg;
|
|
||||||
try {
|
|
||||||
const data = optimize(originSvg);
|
|
||||||
optimizedSvg = data.data;
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
const JSXContent = await getJSXContent(
|
|
||||||
pascalCase(icon),
|
|
||||||
optimizedSvg
|
|
||||||
);
|
|
||||||
|
|
||||||
const iconName = path.basename(icon, '.svg');
|
|
||||||
await writeFile(
|
|
||||||
path.resolve(folder, `${iconName}.tsx`),
|
|
||||||
JSXContent,
|
|
||||||
{ encoding: 'utf8', flag: '' }
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log('Generated:', iconName);
|
|
||||||
});
|
|
||||||
|
|
||||||
await Promise.allSettled([
|
|
||||||
...generateIcons,
|
|
||||||
generateImportEntry(icons, folder),
|
|
||||||
]);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function optimize(input) {
|
|
||||||
return svgo.optimize(input, {
|
|
||||||
plugins: [
|
|
||||||
'preset-default',
|
|
||||||
'prefixIds',
|
|
||||||
{
|
|
||||||
name: 'sortAttrs',
|
|
||||||
params: {
|
|
||||||
xmlnsOrder: 'alphabetical',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get icon component template
|
|
||||||
*
|
|
||||||
* @param {string} name
|
|
||||||
*/
|
|
||||||
async function getJSXContent(name, svgCode) {
|
|
||||||
let svgrContent = '';
|
|
||||||
try {
|
|
||||||
svgrContent = await svgr.transform(
|
|
||||||
svgCode,
|
|
||||||
{
|
|
||||||
icon: true,
|
|
||||||
typescript: true,
|
|
||||||
},
|
|
||||||
{ componentName: `${name}Icon1` }
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
let matcher = svgrContent.match(/<svg ([^\>]+)>([\s\S]*?)<\/svg>/);
|
|
||||||
return `
|
|
||||||
import { SvgIcon, SvgIconProps } from '@mui/material';
|
|
||||||
export const ${name}Icon = (props: SvgIconProps) => (
|
|
||||||
<SvgIcon ${matcher[1]}>
|
|
||||||
${matcher[2]}
|
|
||||||
</SvgIcon>
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateImportEntry(iconNodes, folder) {
|
|
||||||
const fileWithImportsPath = path.resolve(folder, 'index.ts');
|
|
||||||
|
|
||||||
const importsContent = iconNodes
|
|
||||||
.map(iconNode => {
|
|
||||||
const iconName = paramCase(iconNode.name);
|
|
||||||
if (!iconName) {
|
|
||||||
return `// Error: ${iconNode.name}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return `export * from './${iconName}/${iconName}';`;
|
|
||||||
})
|
|
||||||
.join('\n');
|
|
||||||
|
|
||||||
await fs.writeFile(
|
|
||||||
fileWithImportsPath,
|
|
||||||
`export const timestamp = ${Date.now()};\n${importsContent}`,
|
|
||||||
{ encoding: 'utf8' }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*} options
|
|
||||||
* @param {array} options.assets
|
|
||||||
* @param {string} options.assets.folder
|
|
||||||
* @param {*} context
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
exports['default'] = async function svgo(options, context) {
|
|
||||||
const libRoot = context.workspace.projects[context.projectName].root;
|
|
||||||
await Promise.allSettled(
|
|
||||||
(options.assets || []).map(async (asset, index) => {
|
|
||||||
await optimizeSvg(path.resolve(libRoot, asset.folder));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user