init: the first public commit for AFFiNE

This commit is contained in:
DarkSky
2022-07-22 15:49:21 +08:00
commit e3e3741393
1451 changed files with 108124 additions and 0 deletions
@@ -0,0 +1,84 @@
import { styled } from '@toeverything/components/ui';
import { FC, useMemo } from 'react';
interface CheckBoxProps {
size?: number;
height?: number;
checked?: boolean;
onChange: (checked: boolean) => void;
}
export const CheckBox: FC<CheckBoxProps> = ({
size = '16px',
height = '23px',
checked,
onChange,
}) => {
const dynamic_style = useMemo(
() => ({
height: {
height,
},
size: {
width: size,
height: size,
},
}),
[height, size]
);
return (
<CheckBoxContainer
style={dynamic_style.height}
onClick={() => onChange(!checked)}
>
<div
style={dynamic_style.size}
className={`checkBox ${checked ? 'checked' : 'unChecked'}`}
/>
</CheckBoxContainer>
);
};
const CheckBoxContainer = styled('div')(() => ({
display: 'inline-flex',
justifyContent: 'center',
alignItems: 'center',
'.checkBox': {
position: 'relative',
borderWidth: '1.5px',
borderRadius: '3px',
cursor: 'pointer',
userSelect: 'none',
// Align border
marginLeft: '1.5px',
transitionDuration: '.2s',
transitionTimingFunction: 'ease-in',
},
'.unChecked': {
borderColor: '#c4c7cc',
borderStyle: 'solid',
backgroundColor: '#fff',
'&:hover': {
boxShadow: 'inset 0 0 4px rgba(165, 124, 124, 0.2)',
},
},
'.checked': {
backgroundColor: '#B9CAD5',
borderColor: '#B9CAD5',
'&::before': {
content: '""',
position: 'absolute',
top: '4px',
bottom: '0px',
left: '4px',
right: '0px',
width: '9px',
height: '5px',
borderWidth: '1.5px',
borderStyle: 'solid',
borderColor: '#fff',
borderTop: 'none',
borderRight: 'none',
transform: 'rotate(-45deg)',
},
},
}));
@@ -0,0 +1,164 @@
import { TextProps } from '@toeverything/components/common';
import { styled } from '@toeverything/components/ui';
import {
ContentColumnValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { AsyncBlock, type CreateView } from '@toeverything/framework/virgo';
import { useRef, type FC } from 'react';
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: FC<CreateView> = ({ block, editor }) => {
const properties = { ...defaultTodoProps, ...block.getProperties() };
const text_ref = useRef<ExtendedTextUtils>(null);
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(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 (
<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>
);
};
const TodoBlock = styled('div')({
display: 'flex',
'.checkBoxContainer': {
marginRight: '4px',
height: '22px',
},
'.textContainer': {
flex: 1,
maxWidth: '100%',
overflowX: 'hidden',
overflowY: 'hidden',
},
'.checked': {
color: '#B9CAD5',
},
});
@@ -0,0 +1,99 @@
import {
BaseView,
getTextProperties,
AsyncBlock,
SelectBlock,
getTextHtml,
} from '@toeverything/framework/virgo';
// import type { CreateView } from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
import { withTreeViewChildren } from '../../utils/WithTreeViewChildren';
import { TodoView, defaultTodoProps } from './TodoView';
import type { TodoAsyncBlock } from './types';
export class TodoBlock extends BaseView {
type = Protocol.Block.Type.todo;
// View = withTreeViewChildren((props: CreateView) => <TodoView {...props} />);
View = withTreeViewChildren(TodoView);
// //
// override ChildrenView = WithTreeViewChildren;
override async onCreate(block: TodoAsyncBlock) {
if (!block.getProperty('text')) {
await block.setProperties(defaultTodoProps);
}
return block;
}
override getSelProperties(
block: TodoAsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
}
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'UL') {
const result = [];
for (let i = 0; i < el.children.length; i++) {
const blocks_info = parseEl(el.children[i]);
result.push(...blocks_info);
}
return result.length > 0 ? result : null;
}
if (tag_name == 'LI' && el.textContent.startsWith('[ ] ')) {
const childNodes = el.childNodes;
let texts = [];
const children = [];
for (let i = 0; i < childNodes.length; i++) {
const blocks_info = parseEl(childNodes[i] as Element);
for (let j = 0; j < blocks_info.length; j++) {
if (blocks_info[j].type === 'text') {
const block_texts =
blocks_info[j].properties.text.value;
texts.push(...block_texts);
} else {
children.push(blocks_info[j]);
}
}
}
if (texts.length > 0 && (texts[0].text || '').startsWith('[ ] ')) {
texts[0].text = texts[0].text.substring('[ ] '.length);
if (!texts[0].text) {
texts = texts.slice(1);
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
children: children,
},
];
}
return null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
let content = getTextHtml(block);
content += await generateHtml(children);
return `<ul><li>[ ] ${content}</li></ul>`;
}
}
@@ -0,0 +1,19 @@
import { AsyncBlock } from '@toeverything/framework/virgo';
import type {
ContentColumnValue,
BooleanColumnValue,
} from '@toeverything/datasource/db-service';
export interface TodoProperties {
text: ContentColumnValue;
checked?: BooleanColumnValue;
collapsed?: BooleanColumnValue;
}
export class TodoAsyncBlock extends AsyncBlock {
override setProperties(
properties: Partial<TodoProperties>
): Promise<boolean> {
return super.setProperties(properties);
}
}