mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 09:06:19 +08:00
182 lines
5.6 KiB
TypeScript
182 lines
5.6 KiB
TypeScript
import { TextProps } from '@toeverything/components/common';
|
|
import {
|
|
AsyncBlock,
|
|
BlockPendantProvider,
|
|
CreateView,
|
|
useOnSelect,
|
|
} from '@toeverything/components/editor-core';
|
|
import { styled } from '@toeverything/components/ui';
|
|
import {
|
|
ContentColumnValue,
|
|
Protocol,
|
|
} from '@toeverything/datasource/db-service';
|
|
import { useRef, useState } from 'react';
|
|
import { BlockContainer } from '../../components/BlockContainer';
|
|
import {
|
|
TextManage,
|
|
type ExtendedTextUtils,
|
|
} from '../../components/text-manage';
|
|
import { tabBlock } from '../../utils/indent';
|
|
import { CheckBox } from './CheckBox';
|
|
import type { TodoAsyncBlock, TodoProperties } from './types';
|
|
|
|
export const defaultTodoProps: TodoProperties = {
|
|
text: { value: [{ text: '' }] },
|
|
};
|
|
|
|
const reset_todo_state = async (block: TodoAsyncBlock) => {
|
|
await block.setProperties({
|
|
checked: { value: false },
|
|
collapsed: { value: false },
|
|
});
|
|
};
|
|
|
|
const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
|
|
const todoValue = contentValue.value;
|
|
return (
|
|
todoValue.length === 0 ||
|
|
(todoValue.length === 1 && !todoValue[0]['text'])
|
|
);
|
|
};
|
|
|
|
export const TodoView = ({ block, editor }: CreateView) => {
|
|
const properties = { ...defaultTodoProps, ...block.getProperties() };
|
|
const text_ref = useRef<ExtendedTextUtils>(null);
|
|
const [isSelect, setIsSelect] = useState<boolean>(false);
|
|
useOnSelect(block.id, (isSelect: boolean) => {
|
|
setIsSelect(isSelect);
|
|
});
|
|
|
|
const turn_into_text_block = async () => {
|
|
// Convert to text block
|
|
await block.setType('text');
|
|
await reset_todo_state(block);
|
|
|
|
if (!text_ref.current) {
|
|
throw new Error(
|
|
'Failed to set cursor position! text_ref is not exist!'
|
|
);
|
|
}
|
|
const currentSelection = text_ref.current.getCurrentSelection();
|
|
if (!currentSelection) {
|
|
throw new Error(
|
|
'Failed to get cursor selection! currentSelection is not exist!'
|
|
);
|
|
}
|
|
// Update cursor position
|
|
editor.selectionManager.setNodeActiveSelection(block.id, {
|
|
type: 'Range',
|
|
info: currentSelection,
|
|
});
|
|
};
|
|
|
|
const on_text_enter: TextProps['handleEnter'] = async props => {
|
|
const { splitContents, isShiftKey } = props;
|
|
if (isShiftKey) {
|
|
return false;
|
|
}
|
|
const { contentBeforeSelection, contentAfterSelection } = splitContents;
|
|
const before = [...contentBeforeSelection.content];
|
|
const after = [...contentAfterSelection.content];
|
|
if (todoIsEmpty({ value: before } as ContentColumnValue)) {
|
|
await turn_into_text_block();
|
|
return true;
|
|
}
|
|
|
|
// Move children to new block
|
|
const children = (await block.children()).filter(
|
|
Boolean
|
|
) as AsyncBlock[];
|
|
|
|
const next_node = await editor.createBlock(Protocol.Block.Type.todo);
|
|
if (!next_node) {
|
|
throw new Error('Failed to create todo block');
|
|
}
|
|
await block.removeChildren();
|
|
await next_node.append(...children);
|
|
await next_node.setProperties({
|
|
text: { value: after } as ContentColumnValue,
|
|
});
|
|
|
|
await block.setProperties({
|
|
text: { value: before } as ContentColumnValue,
|
|
collapsed: { value: false },
|
|
});
|
|
await block.after(next_node);
|
|
|
|
editor.selectionManager.activeNodeByNodeId(next_node.id);
|
|
|
|
return true;
|
|
};
|
|
|
|
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
|
|
await tabBlock(editor, block, isShiftKey);
|
|
return true;
|
|
};
|
|
|
|
const on_backspace: TextProps['handleBackSpace'] = async ({
|
|
isCollAndStart,
|
|
}) => {
|
|
if (!isCollAndStart) {
|
|
return false;
|
|
}
|
|
await turn_into_text_block();
|
|
return true;
|
|
};
|
|
|
|
const on_checked_change = async (checked: boolean) => {
|
|
await block.setProperties({
|
|
checked: { value: checked },
|
|
});
|
|
};
|
|
|
|
return (
|
|
<BlockContainer editor={editor} block={block} selected={isSelect}>
|
|
<BlockPendantProvider editor={editor} block={block}>
|
|
<TodoBlock>
|
|
<div className={'checkBoxContainer'}>
|
|
<CheckBox
|
|
checked={properties.checked?.value}
|
|
onChange={on_checked_change}
|
|
/>
|
|
</div>
|
|
|
|
<div className={'textContainer'}>
|
|
<TextManage
|
|
className={
|
|
properties.checked?.value ? 'checked' : ''
|
|
}
|
|
ref={text_ref}
|
|
editor={editor}
|
|
block={block}
|
|
supportMarkdown
|
|
placeholder="To-do"
|
|
handleEnter={on_text_enter}
|
|
handleBackSpace={on_backspace}
|
|
handleTab={on_tab}
|
|
/>
|
|
</div>
|
|
</TodoBlock>
|
|
</BlockPendantProvider>
|
|
</BlockContainer>
|
|
);
|
|
};
|
|
|
|
const TodoBlock = styled('div')({
|
|
display: 'flex',
|
|
'.checkBoxContainer': {
|
|
marginRight: '4px',
|
|
padding: '0 4px',
|
|
height: '22px',
|
|
},
|
|
'.textContainer': {
|
|
flex: 1,
|
|
maxWidth: '100%',
|
|
overflowX: 'hidden',
|
|
overflowY: 'hidden',
|
|
},
|
|
'.checked': {
|
|
color: '#B9CAD5',
|
|
},
|
|
});
|