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,164 @@
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
CreateView,
getTextHtml,
getTextProperties,
SelectBlock,
} from '@toeverything/framework/virgo';
import { FC } from 'react';
import { TextView } from './TextView';
export class QuoteBlock extends BaseView {
type = Protocol.Block.Type.quote;
View: FC<CreateView> = TextView;
// override ChildrenView = IndentWrapper;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
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 === 'BLOCKQUOTE') {
const childNodes = el.childNodes;
const texts = [];
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);
}
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
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 `<blockquote>${content}</blockquote>`;
}
}
export class CalloutBlock extends BaseView {
type = Protocol.Block.Type.callout;
View: FC<CreateView> = TextView;
// override ChildrenView = IndentWrapper;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
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 === 'ASIDE' ||
el.firstChild?.nodeValue?.startsWith('<aside>')
) {
const childNodes = el.childNodes;
let texts = [];
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);
}
}
}
if (
texts.length > 0 &&
(texts[0].text || '').startsWith('<aside>')
) {
texts[0].text = texts[0].text.substring('<aside>'.length);
if (!texts[0].text) {
texts = texts.slice(1);
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
children: [],
},
];
}
if (el.firstChild?.nodeValue?.startsWith('</aside>')) {
return [];
}
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 `<aside>${content}</aside>`;
}
}
@@ -0,0 +1,327 @@
import {
BaseView,
CreateView,
AsyncBlock,
getTextProperties,
SelectBlock,
getTextHtml,
} from '@toeverything/framework/virgo';
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { TextView } from './TextView';
import { FC } from 'react';
import { getRandomString } from '@toeverything/components/common';
export class TextBlock extends BaseView {
type = Protocol.Block.Type.text;
View: FC<CreateView> = TextView;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
}
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
if (el instanceof Text) {
// TODO: parsing style
return el.textContent.split('\n').map(text => {
return {
type: this.type,
properties: {
text: { value: [{ text: text }] },
},
children: [],
};
});
}
const tag_name = el.tagName;
const block_style: any = {};
switch (tag_name) {
case 'STRONG':
case 'B':
block_style.bold = true;
break;
case 'A':
block_style.type = 'link';
block_style.url = el.getAttribute('href');
block_style.id = getRandomString('link');
block_style.children = [];
break;
case 'EM':
block_style.italic = true;
break;
case 'U':
block_style.underline = true;
break;
case 'CODE':
block_style.inlinecode = true;
break;
case 'S':
case 'DEL':
block_style.strikethrough = true;
break;
default:
break;
}
const child_nodes = el.childNodes;
let texts = [];
if (Object.keys(block_style).length > 0) {
for (let i = 0; i < child_nodes.length; i++) {
const blocks_info: any[] = parseEl(child_nodes[i] as Element);
for (let j = 0; j < blocks_info.length; j++) {
const block = blocks_info[j];
if (block.type === this.type) {
const block_texts = block.properties.text.value.map(
(text_value: any) => {
return tag_name === 'A'
? { ...text_value }
: { ...block_style, ...text_value };
}
);
texts.push(...block_texts);
}
}
}
}
if (tag_name === 'A') {
block_style.children.push(...texts);
texts = [block_style];
}
return texts.length > 0
? [
{
type: this.type,
properties: {
text: { value: texts },
},
children: [],
},
]
: null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
let content = getTextHtml(block);
content += await generateHtml(children);
return `<p>${content}</p>`;
}
}
export class Heading1Block extends BaseView {
type = Protocol.Block.Type.heading1;
View: FC<CreateView> = TextView;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
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 === 'H1') {
const childNodes = el.childNodes;
const texts = [];
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);
}
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
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 `<h1>${content}</h1>`;
}
}
export class Heading2Block extends BaseView {
type = Protocol.Block.Type.heading2;
View: FC<CreateView> = TextView;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
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 === 'H2') {
const childNodes = el.childNodes;
const texts = [];
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);
}
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
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 `<h2>${content}</h2>`;
}
}
export class Heading3Block extends BaseView {
type = Protocol.Block.Type.heading3;
View: FC<CreateView> = TextView;
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
if (!block.getProperty('text')) {
await block.setProperty('text', {
value: [{ text: '' }],
});
}
return block;
}
override getSelProperties(
block: AsyncBlock,
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 === 'H3') {
const childNodes = el.childNodes;
const texts = [];
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);
}
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
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 `<h3>${content}</h3>`;
}
}
@@ -0,0 +1,251 @@
import { FC, useState } from 'react';
import { CustomText, TextProps } from '@toeverything/components/common';
import {
mergeGroup,
RenderBlockChildren,
splitGroup,
supportChildren,
unwrapGroup,
useOnSelect,
WrapperWithPendantAndDragDrop,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { Protocol } from '@toeverything/datasource/db-service';
import { CreateView } from '@toeverything/framework/virgo';
import { BlockContainer } from '../../components/BlockContainer';
import { IndentWrapper } from '../../components/IndentWrapper';
import { TextManage } from '../../components/text-manage';
import { tabBlock } from '../../utils/indent';
interface CreateTextView extends CreateView {
// TODO: need to optimize
containerClassName?: string;
}
const TextBlock = styled(TextManage)<{ type: string }>(({ theme, type }) => {
const textStyleMap = {
text: theme.affine.typography.body1,
heading1: theme.affine.typography.h1,
heading2: theme.affine.typography.h2,
heading3: theme.affine.typography.h3,
heading4: theme.affine.typography.h4,
callout: {
...theme.affine.typography.body1,
background: theme.affine.palette.textSelected,
},
quote: {
...theme.affine.typography.body1,
borderLeft: `2px solid ${theme.affine.palette.primary}`,
paddingLeft: theme.affine.spacing.xsSpacing,
},
};
if (type in textStyleMap) {
const textType = type as keyof typeof textStyleMap;
return textStyleMap[textType];
} else {
return {
fontSize: textStyleMap.text.fontSize,
lineHeight: textStyleMap.text.lineHeight,
};
}
});
export const TextView: FC<CreateTextView> = ({
block,
editor,
containerClassName,
}) => {
const [isSelect, setIsSelect] = useState<boolean>();
useOnSelect(block.id, (is_select: boolean) => {
setIsSelect(is_select);
});
// block.remove();
const onTextEnter: TextProps['handleEnter'] = async props => {
const { splitContents, isShiftKey } = props;
if (isShiftKey || !splitContents) {
return false;
}
const { contentBeforeSelection, contentAfterSelection } = splitContents;
const before = [...contentBeforeSelection.content];
const after = [...contentAfterSelection.content];
const _nextBlockChildren = await block.children();
const _nextBlock = await editor.createBlock('text');
await _nextBlock.setProperty('text', {
value: after as CustomText[],
});
_nextBlock.append(..._nextBlockChildren);
block.removeChildren();
await block.setProperty('text', {
value: before as CustomText[],
});
await block.after(_nextBlock);
editor.selectionManager.activeNodeByNodeId(_nextBlock.id);
return true;
};
const onBackspace: TextProps['handleBackSpace'] = async props => {
return await editor.withSuspend(async () => {
const { isCollAndStart } = props;
if (!isCollAndStart) {
return false;
}
if (block.type !== 'text') {
await block.setType('text');
return true;
}
const parentBlock = await block.parent();
if (!parentBlock) {
return false;
}
if (Protocol.Block.Type.group === parentBlock.type) {
const children = await block.children();
const preNode = await block.physicallyPerviousSibling();
// FIXME support children do not means has textBlock
// TODO: abstract this part of code
if (preNode) {
if (supportChildren(preNode)) {
editor.suspend(true);
await editor.selectionManager.activePreviousNode(
block.id,
'end'
);
const value = [
...preNode.getProperty('text').value,
...block.getProperty('text').value,
];
await preNode.setProperty('text', {
value,
});
await preNode.append(...children);
await block.remove();
editor.suspend(false);
} else {
// TODO: point does not clear
await editor.selectionManager.activePreviousNode(
block.id,
'start'
);
if (block.blockProvider.isEmpty()) {
block.remove();
}
}
return true;
} else {
// TODO remove timing problem
const prevGroupBlock = await parentBlock.previousSibling();
if (!prevGroupBlock) {
const childrenBlock = await parentBlock.children();
if (childrenBlock.length) {
if (children.length) {
await parentBlock.append(...children);
}
await block.remove();
return true;
}
parentBlock.remove();
return true;
}
if (prevGroupBlock.type !== Protocol.Block.Type.group) {
unwrapGroup(parentBlock);
return true;
}
mergeGroup(prevGroupBlock, parentBlock);
return true;
}
}
if (Protocol.Block.Type.gridItem === parentBlock.type) {
const siblingBlocks = await parentBlock.children();
const previousSiblings = await block.previousSiblings();
const gridBlock = await parentBlock.parent();
const prevGridItemBlock = await parentBlock.previousSibling();
const siblingBlocksReverse = [...previousSiblings].reverse();
const textBlock = siblingBlocksReverse.find(child =>
supportChildren(child)
);
if (textBlock) {
const children = await block.children();
const value = [
...textBlock.getProperty('text').value,
...block.getProperty('text').value,
];
await textBlock.setProperty('text', {
value,
});
await textBlock.append(...children);
await block.remove();
await editor.selectionManager.activeNodeByNodeId(
textBlock.id
);
} else if (prevGridItemBlock) {
await prevGridItemBlock.append(...siblingBlocks);
await parentBlock.remove();
} else {
await gridBlock.before(...siblingBlocks);
await parentBlock.remove();
}
return true;
} else {
const nextNodes = await block.nextSiblings();
for (const nextNode of nextNodes) {
await nextNode.remove();
}
block.append(...nextNodes);
editor.commands.blockCommands.moveBlockAfter(
block.id,
parentBlock.id
);
}
return true;
});
};
const handleConvert = async (
toType: string,
options?: Record<string, unknown>
) => {
if (toType === Protocol.Block.Type.groupDivider) {
splitGroup(editor, block, true);
return;
}
await block.setType(toType as 'title');
await block.setProperty('text', {
value: options?.['text'] as CustomText[],
});
block.firstCreateFlag = true;
};
const onTab: TextProps['handleTab'] = async ({ isShiftKey }) => {
await tabBlock(block, isShiftKey);
return true;
};
return (
<BlockContainer
editor={editor}
block={block}
selected={isSelect}
className={containerClassName}
>
<WrapperWithPendantAndDragDrop editor={editor} block={block}>
<TextBlock
block={block}
type={block.type}
editor={editor}
placeholder={"type '/' for commands"}
handleEnter={onTextEnter}
handleBackSpace={onBackspace}
handleConvert={handleConvert}
handleTab={onTab}
/>
</WrapperWithPendantAndDragDrop>
<IndentWrapper>
<RenderBlockChildren block={block} />
</IndentWrapper>
</BlockContainer>
);
};
@@ -0,0 +1,7 @@
export {
TextBlock,
Heading1Block,
Heading2Block,
Heading3Block,
} from './TextBlock';
export { QuoteBlock, CalloutBlock } from './QuoteBlock';
@@ -0,0 +1,5 @@
import { CustomText } from '@toeverything/components/common';
export interface TextProperties {
text: CustomText[];
}