merge branch develop into branch feat/doublelink220820

This commit is contained in:
xiaodong zuo
2022-09-05 11:45:19 +08:00
175 changed files with 6576 additions and 4627 deletions
@@ -1,29 +1,28 @@
import type { TextProps } from '@toeverything/components/common';
import {
ContentColumnValue,
services,
Protocol,
services,
} from '@toeverything/datasource/db-service';
import { type CreateView } from '@toeverything/framework/virgo';
import { useEffect, useRef, useState } from 'react';
import {
BlockPendantProvider,
RenderBlockChildren,
supportChildren,
useOnSelect,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { BlockContainer } from '../../components/BlockContainer';
import { List } from '../../components/style-container';
import {
TextManage,
type ExtendedTextUtils,
} from '../../components/text-manage';
import { tabBlock } from '../../utils/indent';
import { BulletIcon, getChildrenType, NumberType } from './data';
import { BulletBlock, BulletProperties } from './types';
import {
supportChildren,
RenderBlockChildren,
useOnSelect,
BlockPendantProvider,
} from '@toeverything/components/editor-core';
import { List } from '../../components/style-container';
import { getChildrenType, BulletIcon, NumberType } from './data';
import { IndentWrapper } from '../../components/IndentWrapper';
import { BlockContainer } from '../../components/BlockContainer';
import { styled } from '@toeverything/components/ui';
export const defaultBulletProps: BulletProperties = {
text: { value: [{ text: '' }] },
@@ -189,7 +188,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
return (
<BlockContainer editor={editor} block={block} selected={isSelect}>
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<List>
<BulletLeft>
<BulletIcon numberType={properties.numberType} />
@@ -208,9 +207,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
</div>
</List>
</BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} />
</IndentWrapper>
<RenderBlockChildren block={block} />
</BlockContainer>
);
};
@@ -1,18 +1,17 @@
import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
CreateView,
getTextProperties,
SelectBlock,
getTextHtml,
BlockEditor,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
import { defaultBulletProps, BulletView } from './BulletView';
import { IndentWrapper } from '../../components/IndentWrapper';
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
import { BulletView, defaultBulletProps } from './BulletView';
export class BulletBlock extends BaseView {
public type = Protocol.Block.Type.bullet;
@@ -27,66 +26,44 @@ export class BulletBlock extends BaseView {
}
return block;
}
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
if (element.tagName === 'UL') {
const firstList = element.querySelector('li');
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 === 'UL') {
const result = [];
for (let i = 0; i < el.children.length; i++) {
const blocks_info = parseEl(el.children[i]);
result.push(...blocks_info);
if (!firstList || firstList.innerText.startsWith('[ ] ')) {
return null;
}
return result.length > 0 ? result : null;
const children = Array.from(element.children);
const childrenBlockInfos = (
await Promise.all(
children.map(childElement =>
this.html2block({
editor,
element: childElement,
})
)
)
)
.flat()
.filter(v => v);
return childrenBlockInfos.length ? childrenBlockInfos : null;
}
if (tag_name == 'LI' && !el.textContent.startsWith('[ ] ')) {
const childNodes = el.childNodes;
const 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]);
}
}
}
return [
{
type: this.type,
properties: {
text: { value: texts },
},
children: children,
},
];
}
return null;
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'LI',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<ul><li>${await commonBlock2HtmlContent(props)}</li></ul>`;
}
}
@@ -1,58 +1,54 @@
import { useState, useRef, useEffect } from 'react';
import { StyleWithAtRules } from 'style9';
import { CreateView } from '@toeverything/framework/virgo';
import CodeMirror, { ReactCodeMirrorRef } from './CodeMirror';
import { styled } from '@toeverything/components/ui';
import { javascript } from '@codemirror/lang-javascript';
import { html } from '@codemirror/lang-html';
import { css } from '@codemirror/lang-css';
import { json } from '@codemirror/lang-json';
import { python } from '@codemirror/lang-python';
import { markdown } from '@codemirror/lang-markdown';
import { xml } from '@codemirror/lang-xml';
import { sql, MySQL, PostgreSQL } from '@codemirror/lang-sql';
import { java } from '@codemirror/lang-java';
import { rust } from '@codemirror/lang-rust';
import { cpp } from '@codemirror/lang-cpp';
import { css } from '@codemirror/lang-css';
import { html } from '@codemirror/lang-html';
import { java } from '@codemirror/lang-java';
import { javascript } from '@codemirror/lang-javascript';
import { json } from '@codemirror/lang-json';
import { lezer } from '@codemirror/lang-lezer';
import { markdown } from '@codemirror/lang-markdown';
import { php } from '@codemirror/lang-php';
import { python } from '@codemirror/lang-python';
import { rust } from '@codemirror/lang-rust';
import { MySQL, PostgreSQL, sql } from '@codemirror/lang-sql';
import { xml } from '@codemirror/lang-xml';
import { StreamLanguage } from '@codemirror/language';
import { go } from '@codemirror/legacy-modes/mode/go';
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { lua } from '@codemirror/legacy-modes/mode/lua';
import { swift } from '@codemirror/legacy-modes/mode/swift';
import { tcl } from '@codemirror/legacy-modes/mode/tcl';
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import { vb } from '@codemirror/legacy-modes/mode/vb';
import { powerShell } from '@codemirror/legacy-modes/mode/powershell';
import { brainfuck } from '@codemirror/legacy-modes/mode/brainfuck';
import { stylus } from '@codemirror/legacy-modes/mode/stylus';
import { erlang } from '@codemirror/legacy-modes/mode/erlang';
import { elixir } from 'codemirror-lang-elixir';
import { nginx } from '@codemirror/legacy-modes/mode/nginx';
import { perl } from '@codemirror/legacy-modes/mode/perl';
import { pascal } from '@codemirror/legacy-modes/mode/pascal';
import { liveScript } from '@codemirror/legacy-modes/mode/livescript';
import { scheme } from '@codemirror/legacy-modes/mode/scheme';
import { toml } from '@codemirror/legacy-modes/mode/toml';
import { vbScript } from '@codemirror/legacy-modes/mode/vbscript';
import { clojure } from '@codemirror/legacy-modes/mode/clojure';
import { coffeeScript } from '@codemirror/legacy-modes/mode/coffeescript';
import { dockerFile } from '@codemirror/legacy-modes/mode/dockerfile';
import { erlang } from '@codemirror/legacy-modes/mode/erlang';
import { go } from '@codemirror/legacy-modes/mode/go';
import { julia } from '@codemirror/legacy-modes/mode/julia';
import { liveScript } from '@codemirror/legacy-modes/mode/livescript';
import { lua } from '@codemirror/legacy-modes/mode/lua';
import { nginx } from '@codemirror/legacy-modes/mode/nginx';
import { pascal } from '@codemirror/legacy-modes/mode/pascal';
import { perl } from '@codemirror/legacy-modes/mode/perl';
import { powerShell } from '@codemirror/legacy-modes/mode/powershell';
import { r } from '@codemirror/legacy-modes/mode/r';
import { ruby } from '@codemirror/legacy-modes/mode/ruby';
import { scheme } from '@codemirror/legacy-modes/mode/scheme';
import { shell } from '@codemirror/legacy-modes/mode/shell';
import { stylus } from '@codemirror/legacy-modes/mode/stylus';
import { swift } from '@codemirror/legacy-modes/mode/swift';
import { tcl } from '@codemirror/legacy-modes/mode/tcl';
import { toml } from '@codemirror/legacy-modes/mode/toml';
import { vb } from '@codemirror/legacy-modes/mode/vb';
import { vbScript } from '@codemirror/legacy-modes/mode/vbscript';
import { yaml } from '@codemirror/legacy-modes/mode/yaml';
import { Extension } from '@codemirror/state';
import { Option, Select } from '@toeverything/components/ui';
import {
useOnSelect,
BlockPendantProvider,
useOnSelect,
} from '@toeverything/components/editor-core';
import { copyToClipboard } from '@toeverything/utils';
import { DuplicateIcon } from '@toeverything/components/icons';
import { Option, Select, styled } from '@toeverything/components/ui';
import { CreateView } from '@toeverything/framework/virgo';
import { copyToClipboard } from '@toeverything/utils';
import { elixir } from 'codemirror-lang-elixir';
import { useEffect, useRef, useState } from 'react';
import { StyleWithAtRules } from 'style9';
import CodeMirror, { ReactCodeMirrorRef } from './CodeMirror';
interface CreateCodeView extends CreateView {
style9?: StyleWithAtRules;
@@ -110,13 +106,15 @@ const CodeBlock = styled('div')(({ theme }) => ({
borderRadius: theme.affine.shape.borderRadius,
'&:hover': {
'.operation': {
display: 'flex',
opacity: 1,
},
},
'.operation': {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-between',
opacity: 0,
transition: 'opacity 1.5s',
},
'.copy-block': {
padding: '0px 10px',
@@ -172,7 +170,7 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
editor.selectionManager.activePreviousNode(block.id, 'start');
};
return (
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<CodeBlock
onKeyDown={e => {
e.stopPropagation();
@@ -200,7 +198,8 @@ export const CodeView = ({ block, editor }: CreateCodeView) => {
</div>
<div>
<div className="copy-block" onClick={copyCode}>
<DuplicateIcon></DuplicateIcon>Copy
<DuplicateIcon />
Copy
</div>
</div>
</div>
@@ -1,17 +1,16 @@
import {
BaseView,
AsyncBlock,
getTextProperties,
CreateView,
SelectBlock,
getTextHtml,
BlockEditor,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
import { Protocol } from '@toeverything/datasource/db-service';
import { CodeView } from './CodeView';
import { ComponentType } from 'react';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class CodeBlock extends BaseView {
type = Protocol.Block.Type.code;
@@ -28,56 +27,22 @@ export class CodeBlock extends BaseView {
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'CODE',
});
}
// TODO: internal format not implemented yet
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'CODE') {
const childNodes = el.childNodes;
let text_value = '';
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;
if (block_texts.length > 0) {
text_value += block_texts[0].text;
}
}
}
}
return [
{
type: this.type,
properties: {
text: { value: [{ text: text_value }] },
},
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 `<code>${content}</code>`;
override async block2html(props: Block2HtmlProps) {
return `<code>${await commonBlock2HtmlContent(props)}<code/>`;
}
}
@@ -1,39 +1,35 @@
import {
AsyncBlock,
BaseView,
BlockEditor,
HTML2BlockResult,
SelectBlock,
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { DividerView } from './divider-view';
import { Block2HtmlProps, commonHTML2block } from '../../utils/commonBlockClip';
export class DividerBlock extends BaseView {
type = Protocol.Block.Type.divider;
View = DividerView;
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'HR') {
return [
{
type: this.type,
properties: {
text: {},
},
children: [],
},
];
}
return null;
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'HR',
ignoreEmptyElement: false,
});
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
return `<hr>`;
override async block2html(props: Block2HtmlProps) {
return `<hr/>`;
}
}
@@ -33,7 +33,7 @@ export const EmbedLinkView = (props: EmbedLinkView) => {
};
return (
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<LinkContainer>
{embedLinkUrl ? (
<SourceView
@@ -5,6 +5,7 @@ import {
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { EmbedLinkView } from './EmbedLinkView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
export class EmbedLinkBlock extends BaseView {
public override selectable = true;
@@ -12,36 +13,8 @@ export class EmbedLinkBlock extends BaseView {
type = Protocol.Block.Type.embedLink;
View = EmbedLinkView;
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
return [
{
type: this.type,
properties: {
// TODO: Not sure what value to fill for name
embedLink: {
name: this.type,
value: el.getAttribute('href'),
},
},
children: [],
},
];
}
return null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const figma_url = block.getProperty('embedLink')?.value;
return `<p><a src=${figma_url}>${figma_url}</p>`;
override async block2html({ block }: Block2HtmlProps) {
const url = block.getProperty('embedLink')?.value;
return `<p><a href="${url}">${url}</a></p>`;
}
}
@@ -1,13 +1,12 @@
import { useState } from 'react';
import { CreateView } from '@toeverything/framework/virgo';
import {
useOnSelect,
BlockPendantProvider,
useOnSelect,
} 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 { styled } from '@toeverything/components/ui';
import { LinkContainer } from '../../components/style-container';
import { Upload } from '../../components/upload/upload';
const MESSAGES = {
ADD_FIGMA_LINK: 'Add figma link',
@@ -30,7 +29,7 @@ export const FigmaView = ({ block, editor }: FigmaView) => {
setIsSelect(isSelect);
});
return (
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<LinkContainer>
{figmaUrl ? (
<SourceView
@@ -5,6 +5,7 @@ import {
SelectBlock,
} from '@toeverything/framework/virgo';
import { FigmaView } from './FigmaView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
export class FigmaBlock extends BaseView {
public override selectable = true;
@@ -12,42 +13,8 @@ export class FigmaBlock extends BaseView {
type = Protocol.Block.Type.figma;
View = FigmaView;
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
const href = el.getAttribute('href');
const allowedHosts = ['www.figma.com'];
const host = new URL(href).host;
if (allowedHosts.includes(host)) {
return [
{
type: this.type,
properties: {
// TODO: Not sure what value to fill for name
embedLink: {
name: this.type,
value: el.getAttribute('href'),
},
},
children: [],
},
];
}
}
return null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const figma_url = block.getProperty('embedLink')?.value;
return `<p><a src=${figma_url}>${figma_url}</p>`;
override async block2html({ block }: Block2HtmlProps) {
const figmaUrl = block.getProperty('embedLink')?.value;
return `<p><a href="${figmaUrl}">${figmaUrl}</a></p>`;
}
}
@@ -9,25 +9,22 @@ import {
services,
} from '@toeverything/datasource/db-service';
import { FileView } from './FileView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
export class FileBlock extends BaseView {
public override selectable = true;
public override editable = false;
type = Protocol.Block.Type.file;
View = FileView;
override async block2html({ block }: Block2HtmlProps) {
const fileProperty = block.getProperty('file');
const fileId = fileProperty?.value;
const fileInfo = fileId
? await services.api.file.get(fileId, block.workspace)
: null;
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const file_property =
block.getProperty('file') || ({} as FileColumnValue);
const file_id = file_property.value;
let file_info = null;
if (file_id) {
file_info = await services.api.file.get(file_id, block.workspace);
}
return `<p><a src=${file_info?.url}>${file_property?.name}</p>`;
return fileInfo
? `<p><a href=${fileInfo?.url}>${fileProperty?.name}</a></p>`
: '';
}
}
@@ -1,4 +1,4 @@
import { RenderBlock } from '@toeverything/components/editor-core';
import { RenderBlockChildren } from '@toeverything/components/editor-core';
import { ChildrenView, CreateView } from '@toeverything/framework/virgo';
export const GridItemRender = function (
@@ -6,13 +6,7 @@ export const GridItemRender = function (
) {
const GridItem = function (props: CreateView) {
const { block } = props;
const children = (
<>
{block.childrenIds.map(id => {
return <RenderBlock key={id} blockId={id} />;
})}
</>
);
const children = <RenderBlockChildren block={block} indent={false} />;
return <>{creator({ ...props, children })}</>;
};
return GridItem;
@@ -1,16 +1,16 @@
import { RenderBlock } from '@toeverything/components/editor-core';
import { CreateView } from '@toeverything/framework/virgo';
import React, { useEffect, useRef, useState } from 'react';
import { GridHandle } from './GirdHandle';
import { BlockRender } 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 { debounce, domToRect, Point } from '@toeverything/utils';
import clsx from 'clsx';
import React, { useEffect, useRef, useState } from 'react';
import ReactDOM from 'react-dom';
import {
GRID_ITEM_CLASS_NAME,
GRID_ITEM_CONTENT_CLASS_NAME,
} from '../grid-item/GridItem';
import { debounce, domToRect, Point } from '@toeverything/utils';
import clsx from 'clsx';
import { Protocol } from '@toeverything/datasource/db-service';
import { GridHandle } from './GirdHandle';
const DB_UPDATE_DELAY = 50;
const GRID_ON_DRAG_CLASS = 'grid-layout-on-drag';
@@ -226,7 +226,7 @@ export const Grid = function (props: CreateView) {
key={id}
className={GRID_ITEM_CLASS_NAME}
>
<RenderBlock hasContainer={false} blockId={id} />
<BlockRender hasContainer={false} blockId={id} />
<GridHandle
onDrag={event => handleDragGrid(event, i)}
editor={editor}
@@ -6,6 +6,10 @@ import {
SelectBlock,
} from '@toeverything/framework/virgo';
import { GroupView } from './GroupView';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
} from '../../utils/commonBlockClip';
export class Group extends BaseView {
public override selectable = true;
@@ -25,13 +29,12 @@ export class Group extends BaseView {
override async onCreate(block: AsyncBlock): Promise<AsyncBlock> {
return block;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const content = await generateHtml(children);
return `<div>${content}<div>`;
override async block2html({ editor, selectInfo, block }: Block2HtmlProps) {
const childrenHtml =
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
block,
selectInfo?.children
);
return `<div>${childrenHtml}<code/>`;
}
}
@@ -21,19 +21,6 @@ const SceneMap: Record<RecastScene, ComponentType<CreateView>> = {
kanban: SceneKanban,
} as const;
const GroupBox = styled('div')(({ theme }) => {
return {
'&:hover': {
// Workaround referring to other components
// See https://emotion.sh/docs/styled#targeting-another-emotion-component
// [GroupActionWrapper.toString()]: {},
'& > *': {
visibility: 'visible',
},
},
};
});
const GroupActionWrapper = styled('div')(({ theme }) => ({
height: '30px',
display: 'flex',
@@ -59,6 +46,14 @@ const GroupActionWrapper = styled('div')(({ theme }) => ({
},
}));
const GroupBox = styled('div')({
'&:hover': {
[GroupActionWrapper.toString()]: {
visibility: 'visible',
},
},
});
const GroupContainer = styled('div')<{ isSelect?: boolean }>(
({ isSelect, theme }) => ({
background: theme.affine.palette.white,
@@ -2,5 +2,5 @@ import { RenderBlockChildren } from '@toeverything/components/editor-core';
import type { CreateView } from '@toeverything/framework/virgo';
export const ScenePage = ({ block }: CreateView) => {
return <RenderBlockChildren block={block} />;
return <RenderBlockChildren block={block} indent={false} />;
};
@@ -41,7 +41,7 @@ export const AddViewMenu = () => {
onClick={() => setActivePanel(!activePanel)}
>
<AddViewIcon fontSize="small" />
<span>Add View</span>
<span style={{ userSelect: 'none' }}>Add View</span>
{activePanel && (
<Panel>
<PanelItem>
@@ -66,10 +66,10 @@ export const AddViewMenu = () => {
key={name}
active={viewType === scene}
onClick={() => {
if (scene === RecastScene.Table) {
// The table view is under progress
return;
}
// if (scene === RecastScene.Table) {
// // The table view is under progress
// return;
// }
setViewType(scene);
}}
style={{ textTransform: 'uppercase' }}
@@ -20,7 +20,7 @@ export const ViewsMenu = () => {
useRecastView();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
setViewName(e.target.value.trim());
setViewName(e.target.value);
};
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
@@ -36,7 +36,7 @@ export const ViewsMenu = () => {
}
await updateView({
...activeView,
name: viewName,
name: viewName.trim(),
type: viewType,
});
setActiveView(null);
@@ -99,10 +99,10 @@ export const ViewsMenu = () => {
key={name}
active={viewType === scene}
onClick={() => {
if (scene === RecastScene.Table) {
// The table view is under progress
return;
}
// if (scene === RecastScene.Table) {
// // The table view is under progress
// return;
// }
setViewType(scene);
}}
style={{ textTransform: 'uppercase' }}
@@ -1,9 +1,5 @@
import { RecastScene } from '@toeverything/components/editor-core';
import {
KanBanIcon,
TableIcon,
TodoListIcon,
} from '@toeverything/components/icons';
import { KanBanIcon, TodoListIcon } from '@toeverything/components/icons';
export const VIEW_LIST = [
{
@@ -16,9 +12,9 @@ export const VIEW_LIST = [
scene: RecastScene.Kanban,
icon: <KanBanIcon fontSize="small" />,
},
{
name: 'Table',
scene: RecastScene.Table,
icon: <TableIcon fontSize="small" />,
},
// {
// name: 'Table',
// scene: RecastScene.Table,
// icon: <TableIcon fontSize="small" />,
// },
] as const;
@@ -1,12 +1,12 @@
import { useCallback } from 'react';
import { CardItem } from './CardItem';
import { styled } from '@toeverything/components/ui';
import { useKanban } from '@toeverything/components/editor-core';
import { CardItemPanelWrapper } from './dndable/wrapper/CardItemPanelWrapper';
import type {
KanbanCard,
KanbanGroup,
} from '@toeverything/components/editor-core';
import { useKanban } from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { useCallback } from 'react';
import { CardItem } from './CardItem';
import { CardItemPanelWrapper } from './dndable/wrapper/CardItemPanelWrapper';
const AddCardWrapper = styled('div')({
display: 'flex',
@@ -48,7 +48,7 @@ export const CardContext = (props: Props) => {
item={item}
active={activeId === id}
>
<CardItem id={id} block={block} />
<CardItem block={block} />
</CardItemPanelWrapper>
</StyledCardContainer>
);
@@ -1,6 +1,6 @@
import {
KanbanBlockRender,
KanbanCard,
RenderBlock,
useEditor,
useKanban,
} from '@toeverything/components/editor-core';
@@ -10,7 +10,6 @@ import {
MuiClickAwayListener,
styled,
} from '@toeverything/components/ui';
import { useFlag } from '@toeverything/datasource/feature-flags';
import { useState, type MouseEvent } from 'react';
import { useRefPage } from './RefPage';
@@ -82,41 +81,37 @@ const Overlay = styled('div')({
},
});
export const CardItem = ({
id,
block,
}: {
id: KanbanCard['id'];
block: KanbanCard['block'];
}) => {
export const CardItem = ({ block }: { block: KanbanCard['block'] }) => {
const { addSubItem } = useKanban();
const { openSubPage } = useRefPage();
const [editable, setEditable] = useState(false);
const showKanbanRefPageFlag = useFlag('ShowKanbanRefPage', false);
const [editableBlock, setEditableBlock] = useState<string | null>(null);
const { editor } = useEditor();
const onAddItem = async () => {
setEditable(true);
await addSubItem(block);
const newItem = await addSubItem(block);
setEditableBlock(newItem.id);
};
const onClickCard = async () => {
openSubPage(id);
openSubPage(block.id);
};
const onClickPen = (e: MouseEvent<Element>) => {
e.stopPropagation();
setEditable(true);
setEditableBlock(block.id);
editor.selectionManager.activeNodeByNodeId(block.id);
};
return (
<MuiClickAwayListener onClickAway={() => setEditable(false)}>
<MuiClickAwayListener onClickAway={() => setEditableBlock(null)}>
<CardContainer>
<CardContent>
<RenderBlock blockId={id} />
<KanbanBlockRender
blockId={block.id}
activeBlock={editableBlock}
/>
</CardContent>
{showKanbanRefPageFlag && !editable && (
{!editableBlock && (
<Overlay onClick={onClickCard}>
<IconButton backgroundColor="#fff" onClick={onClickPen}>
<PenIcon />
@@ -21,13 +21,17 @@ const Modal = ({ open, children }: { open: boolean; children?: ReactNode }) => {
return createPortal(
<MuiBackdrop
open={open}
onMouseDown={(e: { stopPropagation: () => void }) => {
// Prevent trigger the bottom editor's selection
e.stopPropagation();
}}
onClick={closeSubPage}
style={{
display: 'flex',
flexDirection: 'column',
background: 'rgba(58, 76, 92, 0.4)',
zIndex: theme.affine.zIndex.popover,
}}
onClick={closeSubPage}
>
<Dialog
onClick={(e: { stopPropagation: () => void }) => {
@@ -1,7 +1,6 @@
import { CardItemWrapper } from '../wrapper/CardItemWrapper';
import { CardItem } from '../../CardItem';
import type { KanbanCard } from '@toeverything/components/editor-core';
import type { DndableItems } from '../type';
import { CardItemWrapper } from '../wrapper/CardItemWrapper';
export function renderContainerDragOverlay({
containerId,
@@ -18,7 +17,7 @@ export function renderContainerDragOverlay({
return (
<CardItemWrapper
key={id}
card={<CardItem key={id} id={id} block={block} />}
card={<CardItem key={id} block={block} />}
index={index}
/>
);
@@ -5,35 +5,13 @@ import {
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { GroupDividerView } from './groupDividerView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
export class GroupDividerBlock extends BaseView {
type = Protocol.Block.Type.groupDivider;
View = GroupDividerView;
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'HR') {
return [
{
type: this.type,
properties: {
text: {},
},
children: [],
},
];
}
return null;
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
return `<hr>`;
override async block2html(props: Block2HtmlProps) {
return `<hr/>`;
}
}
@@ -165,12 +165,10 @@ export const ImageView = ({ block, editor }: ImageView) => {
};
return (
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<ImageBlock>
<div style={{ position: 'relative' }} ref={resizeBox}>
{!isSelect ? (
<ImageShade onClick={handleClick}></ImageShade>
) : null}
{!isSelect ? <ImageShade onClick={handleClick} /> : null}
{imgUrl ? (
<div
onMouseDown={e => {
@@ -1,10 +1,12 @@
import {
AsyncBlock,
BaseView,
SelectBlock,
BlockEditor,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { ImageView } from './ImageView';
import { Block2HtmlProps } from '../../utils/commonBlockClip';
import { getRandomString } from '@toeverything/components/common';
export class ImageBlock extends BaseView {
public override selectable = true;
@@ -13,42 +15,40 @@ export class ImageBlock extends BaseView {
View = ImageView;
// TODO: needs to download the image and then upload it to get a new link and then assign it
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'IMG') {
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
if (element.tagName === 'IMG') {
return [
{
type: this.type,
properties: {
value: '',
url: el.getAttribute('src'),
name: el.getAttribute('src'),
size: 0,
type: 'link',
image: {
value: getRandomString('image'),
url: element.getAttribute('src'),
name: element.getAttribute('src'),
size: 0,
type: 'link',
},
},
children: [],
},
];
}
return null;
}
// TODO:
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const text = block.getProperty('text');
override async block2html({ block, editor }: Block2HtmlProps) {
const textValue = block.getProperty('text');
const content = '';
if (text) {
text.value.map(text => `<span>${text}</span>`).join('');
}
// TODO: child
return `<p><img src=${content}></p>`;
// TODO: text.value should export with style??
const figcaption = (textValue?.value ?? [])
.map(({ text }) => `<span>${text}</span>`)
.join('');
return `<figure><img src="${content}" alt="${figcaption}"/><figcaption>${figcaption}<figcaption/></figure>`;
}
}
@@ -1,8 +1,8 @@
import { TextProps } from '@toeverything/components/common';
import {
ContentColumnValue,
services,
Protocol,
services,
} from '@toeverything/datasource/db-service';
import { type CreateView } from '@toeverything/framework/virgo';
import { useEffect, useRef, useState } from 'react';
@@ -11,18 +11,17 @@ import {
type ExtendedTextUtils,
} from '../../components/text-manage';
import { tabBlock } from '../../utils/indent';
import { IndentWrapper } from '../../components/IndentWrapper';
import type { Numbered, NumberedAsyncBlock } from './types';
import { getChildrenType, getNumber } from './data';
import {
supportChildren,
RenderBlockChildren,
useOnSelect,
BlockPendantProvider,
RenderBlockChildren,
supportChildren,
useOnSelect,
} from '@toeverything/components/editor-core';
import { List } from '../../components/style-container';
import { BlockContainer } from '../../components/BlockContainer';
import { List } from '../../components/style-container';
import { getChildrenType, getNumber } from './data';
export const defaultTodoProps: Numbered = {
text: { value: [{ text: '' }] },
@@ -184,7 +183,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
return (
<BlockContainer editor={editor} block={block} selected={isSelect}>
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<List>
<div className={'checkBoxContainer'}>
{getNumber(properties.numberType, number)}.
@@ -204,9 +203,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
</List>
</BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} />
</IndentWrapper>
<RenderBlockChildren block={block} />
</BlockContainer>
);
};
@@ -1,17 +1,16 @@
import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
getTextProperties,
SelectBlock,
getTextHtml,
BlockEditor,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
// import { withTreeViewChildren } from '../../utils/with-tree-view-children';
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
import { defaultTodoProps, NumberedView } from './NumberedView';
import { IndentWrapper } from '../../components/IndentWrapper';
export class NumberedBlock extends BaseView {
public type = Protocol.Block.Type.numbered;
@@ -29,71 +28,39 @@ export class NumberedBlock extends BaseView {
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 === 'OL') {
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;
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
if (element.tagName === 'OL') {
const children = Array.from(element.children);
const childrenBlockInfos = (
await Promise.all(
children.map(childElement =>
this.html2block({
editor,
element: childElement,
})
)
)
)
.flat()
.filter(v => v);
return childrenBlockInfos.length ? childrenBlockInfos : 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;
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'LI',
});
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
let content = getTextHtml(block);
content += await generateHtml(children);
return `<ol><li>${content}</li></ol>`;
override async block2html(props: Block2HtmlProps) {
return `<ol><li>${await commonBlock2HtmlContent(props)}</li></ol>`;
}
}
@@ -1,14 +1,14 @@
import { useRef, useEffect, useMemo, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router';
import { BackLink, TextProps } from '@toeverything/components/common';
import {
RenderBlockChildren,
BlockPendantProvider,
RenderBlockChildren,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { ContentColumnValue } from '@toeverything/datasource/db-service';
import { CreateView } from '@toeverything/framework/virgo';
import { Theme, styled } from '@toeverything/components/ui';
import {
TextManage,
@@ -81,7 +81,7 @@ export const PageView = ({ block, editor }: CreateView) => {
return (
<PageTitleBlock>
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<TextManage
alwaysShowPlaceholder
ref={textRef}
@@ -1,21 +1,9 @@
import { withRecastBlock } from '@toeverything/components/editor-core';
import {
Protocol,
DefaultColumnsValue,
} from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
ChildrenView,
getTextHtml,
getTextProperties,
SelectBlock,
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock, BaseView } from '@toeverything/framework/virgo';
import { PageView } from './PageView';
export const PageChildrenView: (prop: ChildrenView) => JSX.Element = props =>
props.children;
import { Block2HtmlProps } from '../../utils/commonBlockClip';
export class PageBlock extends BaseView {
type = Protocol.Block.Type.page;
@@ -35,21 +23,17 @@ export class PageBlock extends BaseView {
return this.get_decoration<any>(content, 'text')?.value?.[0].text;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
const content = getTextHtml(block);
const childrenContent = await generateHtml(children);
return `<h1>${content}</h1> ${childrenContent}`;
override async block2html({ block, editor, selectInfo }: Block2HtmlProps) {
const header =
await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo(
block,
selectInfo
);
const childrenHtml =
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
block,
selectInfo?.children
);
return `<h1>${header}</h1>${childrenHtml}`;
}
}
@@ -1,15 +1,16 @@
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
BlockEditor,
CreateView,
getTextHtml,
getTextProperties,
SelectBlock,
HTML2BlockResult,
} from '@toeverything/framework/virgo';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
import { TextView } from './TextView';
@@ -29,54 +30,25 @@ export class QuoteBlock extends BaseView {
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'BLOCKQUOTE',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<blockquote>${await commonBlock2HtmlContent(
props
)}</blockquote>`;
}
}
@@ -96,69 +68,22 @@ export class CalloutBlock extends BaseView {
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'ASIDE',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<aside>${await commonBlock2HtmlContent(props)}</aside>`;
}
}
@@ -2,17 +2,16 @@ import {
BaseView,
CreateView,
AsyncBlock,
getTextProperties,
SelectBlock,
getTextHtml,
HTML2BlockResult,
BlockEditor,
} from '@toeverything/framework/virgo';
import {
DefaultColumnsValue,
Protocol,
} from '@toeverything/datasource/db-service';
import { Protocol } from '@toeverything/datasource/db-service';
import { TextView } from './TextView';
import { getRandomString } from '@toeverything/components/common';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
export class TextBlock extends BaseView {
type = Protocol.Block.Type.text;
@@ -28,106 +27,30 @@ export class TextBlock extends BaseView {
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>`;
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: [
'DIV',
'P',
'PRE',
'B',
'A',
'EM',
'U',
'CODE',
'S',
'DEL',
],
});
}
}
@@ -144,55 +67,23 @@ export class Heading1Block extends BaseView {
}
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H1',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<h1>${await commonBlock2HtmlContent(props)}</h1>`;
}
}
@@ -209,55 +100,23 @@ export class Heading2Block extends BaseView {
}
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H2',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<h2>${await commonBlock2HtmlContent(props)}</h2>`;
}
}
@@ -275,53 +134,22 @@ export class Heading3Block extends BaseView {
return block;
}
override getSelProperties(
block: AsyncBlock,
selectInfo: any
): DefaultColumnsValue {
const properties = super.getSelProperties(block, selectInfo);
return getTextProperties(properties, selectInfo);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'H3',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<h3>${await commonBlock2HtmlContent(props)}</h3>`;
}
}
@@ -12,7 +12,6 @@ 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 { dedentBlock, tabBlock } from '../../utils/indent';
interface CreateTextView extends CreateView {
@@ -243,7 +242,7 @@ export const TextView = ({
selected={isSelect}
className={containerClassName}
>
<BlockPendantProvider block={block}>
<BlockPendantProvider editor={editor} block={block}>
<TextBlock
block={block}
type={block.type}
@@ -255,9 +254,7 @@ export const TextView = ({
handleTab={onTab}
/>
</BlockPendantProvider>
<IndentWrapper>
<RenderBlockChildren block={block} />
</IndentWrapper>
<RenderBlockChildren block={block} />
</BlockContainer>
);
};
@@ -1,11 +1,17 @@
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 { AsyncBlock, type CreateView } from '@toeverything/framework/virgo';
import { useRef } from 'react';
import { useRef, useState } from 'react';
import { BlockContainer } from '../../components/BlockContainer';
import {
TextManage,
type ExtendedTextUtils,
@@ -36,6 +42,10 @@ const todoIsEmpty = (contentValue: ContentColumnValue): boolean => {
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
@@ -121,28 +131,34 @@ export const TodoView = ({ block, editor }: CreateView) => {
};
return (
<TodoBlock>
<div className={'checkBoxContainer'}>
<CheckBox
checked={properties.checked?.value}
onChange={on_checked_change}
/>
</div>
<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>
<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>
);
};
@@ -1,18 +1,17 @@
import { Protocol } from '@toeverything/datasource/db-service';
import {
BaseView,
getTextProperties,
AsyncBlock,
SelectBlock,
getTextHtml,
BlockEditor,
HTML2BlockResult,
withTreeViewChildren,
} from '@toeverything/framework/virgo';
// import type { CreateView } from '@toeverything/framework/virgo';
import { defaultTodoProps, TodoView } from './TodoView';
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';
Block2HtmlProps,
commonBlock2HtmlContent,
commonHTML2block,
} from '../../utils/commonBlockClip';
import type { TodoAsyncBlock } from './types';
export class TodoBlock extends BaseView {
@@ -29,71 +28,44 @@ export class TodoBlock extends BaseView {
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);
override async html2block({
element,
editor,
}: {
element: Element;
editor: BlockEditor;
}): Promise<HTML2BlockResult> {
if (element.tagName === 'UL') {
const firstList = element.querySelector('li');
if (!firstList || !firstList.innerText.startsWith('[ ] ')) {
return null;
}
return result.length > 0 ? result : null;
const children = Array.from(element.children);
const childrenBlockInfos = (
await Promise.all(
children.map(childElement =>
this.html2block({
editor,
element: childElement,
})
)
)
)
.flat()
.filter(v => v);
return childrenBlockInfos.length ? childrenBlockInfos : 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;
return commonHTML2block({
element,
editor,
type: this.type,
tagName: 'LI',
});
}
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>`;
override async block2html(props: Block2HtmlProps) {
return `<ul><li>[ ] ${await commonBlock2HtmlContent(props)}</li></ul>`;
}
}
@@ -5,6 +5,10 @@ import {
SelectBlock,
} from '@toeverything/framework/virgo';
import { YoutubeView } from './YoutubeView';
import {
Block2HtmlProps,
commonBlock2HtmlContent,
} from '../../utils/commonBlockClip';
export class YoutubeBlock extends BaseView {
public override selectable = true;
@@ -12,42 +16,11 @@ export class YoutubeBlock extends BaseView {
type = Protocol.Block.Type.youtube;
View = YoutubeView;
override html2block(
el: Element,
parseEl: (el: Element) => any[]
): any[] | null {
const tag_name = el.tagName;
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
const href = el.getAttribute('href');
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
const host = new URL(href).host;
if (allowedHosts.includes(host)) {
return [
{
type: this.type,
properties: {
// TODO: is not sure what value to fill in name
embedLink: {
name: this.type,
value: el.getAttribute('href'),
},
},
children: [],
},
];
}
}
return null;
override async block2Text(block: AsyncBlock, selectInfo: SelectBlock) {
return block.getProperty('embedLink')?.value ?? '';
}
override async block2html(
block: AsyncBlock,
children: SelectBlock[],
generateHtml: (el: any[]) => Promise<string>
): Promise<string> {
override async block2html({ block }: Block2HtmlProps) {
const url = block.getProperty('embedLink')?.value;
return `<p><a src=${url}>${url}</p>`;
return `<p><a href="${url}">${url}</a></p>`;
}
}
@@ -23,7 +23,7 @@ export const BlockContainer = function ({
);
};
export const Container = styled('div')<{ selected: boolean }>(
export const Container = styled('div')<{ selected?: boolean }>(
({ selected, theme }) => ({
backgroundColor: selected ? theme.affine.palette.textSelected : '',
marginBottom: '2px',
@@ -1,17 +0,0 @@
import { PropsWithChildren } from 'react';
import { ChildrenView } from '@toeverything/framework/virgo';
import { styled } from '@toeverything/components/ui';
/**
* Indent rendering child nodes
*/
export const IndentWrapper = (props: PropsWithChildren) => {
return <StyledIdentWrapper>{props.children}</StyledIdentWrapper>;
};
const StyledIdentWrapper = styled('div')({
display: 'flex',
flexDirection: 'column',
// TODO: marginLeft should use theme provided by styled
marginLeft: '30px',
});
@@ -1 +0,0 @@
export * from './IndentWrapper';
@@ -1,4 +1,5 @@
export const isYoutubeUrl = (url?: string): boolean => {
if (!url) return false;
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
const host = new URL(url).host;
return allowedHosts.includes(host);
@@ -1,6 +0,0 @@
.v-basic-table-body {
overflow: hidden !important;
&:hover {
overflow: auto !important;
}
}
@@ -1,19 +1,19 @@
import {
useMemo,
memo,
useCallback,
useLayoutEffect,
useMemo,
useRef,
useState,
useLayoutEffect,
useCallback,
} from 'react';
import { VariableSizeGrid, areEqual } from 'react-window';
import type {
GridChildComponentProps,
GridItemKeySelector,
} from 'react-window';
import { VariableSizeGrid } from 'react-window';
import style9 from 'style9';
import './basic-table.scss';
export interface TableColumn {
dataKey: string;
label: string;
@@ -1,236 +0,0 @@
import {
BlockPendantProvider,
CreateView,
RenderBlock,
useCurrentView,
useOnSelect,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import type {
ComponentPropsWithoutRef,
ComponentPropsWithRef,
CSSProperties,
ReactElement,
} from 'react';
import { forwardRef, useState } from 'react';
import { SCENE_CONFIG } from '../blocks/group/config';
import { BlockContainer } from '../components/BlockContainer';
type WithChildrenConfig = {
indent: CSSProperties['marginLeft'];
};
const defaultConfig: WithChildrenConfig = {
indent: '30px',
};
const TreeView = forwardRef<
HTMLDivElement,
{ lastItem?: boolean } & ComponentPropsWithRef<'div'>
>(({ lastItem = false, children, onClick, ...restProps }, ref) => {
return (
<TreeWrapper ref={ref} {...restProps}>
<StyledTreeView>
<VerticalLine last={lastItem} onClick={onClick} />
<HorizontalLine last={lastItem} onClick={onClick} />
{lastItem && <LastItemRadius />}
</StyledTreeView>
{/* maybe need a child wrapper */}
{children}
</TreeWrapper>
);
});
interface ChildrenViewProp {
childrenIds: string[];
handleCollapse: () => void;
indent?: string | number;
}
const ChildrenView = ({
childrenIds,
handleCollapse,
indent,
}: ChildrenViewProp) => {
const [currentView] = useCurrentView();
const isKanbanScene = currentView.type === SCENE_CONFIG.KANBAN;
return (
<Children style={{ ...(!isKanbanScene && { marginLeft: indent }) }}>
{childrenIds.map((childId, idx) => {
if (isKanbanScene) {
return (
<StyledBorder key={childId}>
<RenderBlock blockId={childId} />
</StyledBorder>
);
}
return (
<TreeView
key={childId}
lastItem={idx === childrenIds.length - 1}
onClick={handleCollapse}
>
<RenderBlock key={childId} blockId={childId} />
</TreeView>
);
})}
</Children>
);
};
const CollapsedNode = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<'div'>
>((props, ref) => {
return (
<TreeView ref={ref} lastItem={true} {...props}>
<Collapsed onClick={props.onClick}>···</Collapsed>
</TreeView>
);
});
/**
* Indent rendering child nodes
*/
export const withTreeViewChildren = (
creator: (props: CreateView) => ReactElement,
customConfig: Partial<WithChildrenConfig> = {}
) => {
const config = {
...defaultConfig,
...customConfig,
};
return (props: CreateView) => {
const { block, editor } = props;
const collapsed = block.getProperty('collapsed')?.value;
const childrenIds = block.childrenIds;
const showChildren = !collapsed && childrenIds.length > 0;
const [isSelect, setIsSelect] = useState<boolean>(false);
useOnSelect(block.id, (isSelect: boolean) => {
setIsSelect(isSelect);
});
const handleCollapse = () => {
block.setProperty('collapsed', { value: true });
};
const handleExpand = () => {
block.setProperty('collapsed', { value: false });
};
return (
<BlockContainer
editor={props.editor}
block={block}
selected={isSelect}
className={Wrapper.toString()}
>
<BlockPendantProvider block={block}>
<div>{creator(props)}</div>
</BlockPendantProvider>
{collapsed && (
<CollapsedNode
onClick={handleExpand}
style={{ marginLeft: config.indent }}
/>
)}
{showChildren && (
<ChildrenView
childrenIds={childrenIds}
handleCollapse={handleCollapse}
indent={config.indent}
/>
)}
</BlockContainer>
);
};
};
const Wrapper = styled('div')({ display: 'flex', flexDirection: 'column' });
const Children = Wrapper;
const TREE_COLOR = '#D5DFE6';
// adjust left and right margins of the the tree line
const TREE_LINE_LEFT_OFFSET = '-16px';
// determine the position of the horizontal line by the type of the item
const TREE_LINE_TOP_OFFSET = '20px'; // '50%'
const TREE_LINE_WIDTH = '12px';
const TreeWrapper = styled('div')({
position: 'relative',
});
const StyledTreeView = styled('div')({
position: 'absolute',
left: TREE_LINE_LEFT_OFFSET,
height: '100%',
});
const Line = styled('div')({
position: 'absolute',
cursor: 'pointer',
backgroundColor: TREE_COLOR,
// somehow tldraw would override this
boxSizing: 'content-box!important' as any,
// See [Can I add background color only for padding?](https://stackoverflow.com/questions/14628601/can-i-add-background-color-only-for-padding)
backgroundClip: 'content-box',
backgroundOrigin: 'content-box',
// Increase click hot spot
padding: '10px',
});
const VerticalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
width: '1px',
height: last ? TREE_LINE_TOP_OFFSET : '100%',
paddingTop: 0,
paddingBottom: 0,
transform: 'translate(-50%, 0)',
opacity: last ? 0 : 'unset',
}));
const HorizontalLine = styled(Line)<{ last: boolean }>(({ last }) => ({
width: TREE_LINE_WIDTH,
height: '1px',
paddingLeft: 0,
paddingRight: 0,
top: TREE_LINE_TOP_OFFSET,
transform: 'translate(0, -50%)',
opacity: last ? 0 : 'unset',
}));
const Collapsed = styled('div')({
cursor: 'pointer',
display: 'inline-block',
color: '#98ACBD',
padding: '8px',
});
const LastItemRadius = styled('div')({
boxSizing: 'content-box',
position: 'absolute',
left: '-0.5px',
top: 0,
height: TREE_LINE_TOP_OFFSET,
bottom: '50%',
width: TREE_LINE_WIDTH,
borderWidth: '1px',
borderStyle: 'solid',
borderLeftColor: TREE_COLOR,
borderBottomColor: TREE_COLOR,
borderTop: 'none',
borderRight: 'none',
borderRadius: '0 0 0 3px',
pointerEvents: 'none',
});
const StyledBorder = styled('div')({
border: '1px solid #E0E6EB',
borderRadius: '5px',
margin: '4px',
});
@@ -0,0 +1,57 @@
import {
AsyncBlock,
BlockEditor,
HTML2BlockResult,
SelectBlock,
} from '@toeverything/components/editor-core';
import { BlockFlavorKeys } from '@toeverything/datasource/db-service';
export type Block2HtmlProps = {
editor: BlockEditor;
block: AsyncBlock;
// The selectInfo parameter is not passed when the block is selected in ful, the selectInfo.type is Range
selectInfo?: SelectBlock;
};
export const commonBlock2HtmlContent = async ({
editor,
block,
selectInfo,
}: Block2HtmlProps) => {
const html =
await editor.clipboard.clipboardUtils.convertTextValue2HtmlBySelectInfo(
block,
selectInfo
);
const childrenHtml =
await editor.clipboard.clipboardUtils.convertBlock2HtmlBySelectInfos(
block,
selectInfo?.children
);
return `${html}${childrenHtml}`;
};
export const commonHTML2block = ({
element,
editor,
tagName,
type,
ignoreEmptyElement = true,
}: {
element: Element;
editor: BlockEditor;
tagName: string | string[];
type: BlockFlavorKeys;
ignoreEmptyElement?: boolean;
}): HTML2BlockResult => {
const tagNames = typeof tagName === 'string' ? [tagName] : tagName;
if (tagNames.includes(element.tagName)) {
const res = editor.clipboard.clipboardUtils.commonHTML2Block(
element,
type,
ignoreEmptyElement
);
return res ? [res] : null;
}
return null;
};