Merge branch 'develop' into fix/experience

This commit is contained in:
QiShaoXuan 2022-08-18 16:47:28 +08:00
commit b25016b893
116 changed files with 1355 additions and 1399 deletions

View File

@ -10,6 +10,7 @@
"commit": false,
"commitConvention": "angular",
"contributorsPerLine": 7,
"badgeTemplate": "\n[all-contributors-badge]: https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg?style=flat-square\n",
"contributors": [
{
"login": "darkskygit",
@ -229,6 +230,42 @@
"contributions": [
"doc"
]
},
{
"login": "liby",
"name": "Bryan Lee",
"avatar_url": "https://avatars.githubusercontent.com/u/38807139?v=4",
"profile": "https://liby.github.io/notes",
"contributions": [
"code"
]
},
{
"login": "chenmoonmo",
"name": "Simon Li",
"avatar_url": "https://avatars.githubusercontent.com/u/36295999?v=4",
"profile": "https://github.com/chenmoonmo",
"contributions": [
"code"
]
},
{
"login": "githbq",
"name": "Bob Hu",
"avatar_url": "https://avatars.githubusercontent.com/u/10009709?v=4",
"profile": "https://github.com/githbq",
"contributions": [
"code"
]
},
{
"login": "lucky-chap",
"name": "Quavo",
"avatar_url": "https://avatars.githubusercontent.com/u/67266933?v=4",
"profile": "https://quavo.vercel.app/",
"contributions": [
"doc"
]
}
]
}

View File

@ -26,7 +26,7 @@ module.exports = {
],
scopes: [
{ name: 'selection' },
{ name: 'whiteboard' },
{ name: 'edgeless' },
{ name: 'point' },
{ name: 'group' },
{ name: 'page' },

1
.env
View File

@ -1,4 +1,5 @@
# use for download icon from figma
FIGMA_TOKEN
NODE_ENV
AFFINE_FEATURE_FLAG_TOKEN

View File

@ -25,6 +25,7 @@
"Kanban",
"keyval",
"ligo",
"livedemo",
"lozad",
"mastersthesis",
"nrwl",

View File

@ -18,7 +18,7 @@ See https://github.com/all-?/all-contributors/issues/361#issuecomment-637166066
-->
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-23-orange.svg?style=flat-square
[all-contributors-badge]: https://img.shields.io/badge/all_contributors-27-orange.svg?style=flat-square
<!-- ALL-CONTRIBUTORS-BADGE:END -->
@ -224,6 +224,10 @@ For help, discussion about best practices, or any other conversation that would
<tr>
<td align="center"><a href="https://github.com/westongraham"><img src="https://avatars.githubusercontent.com/u/89493023?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Weston Graham</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=westongraham" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/pointmax"><img src="https://avatars.githubusercontent.com/u/49361135?v=4?s=50" width="50px;" alt=""/><br /><sub><b>pointmax</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=pointmax" title="Documentation">📖</a></td>
<td align="center"><a href="https://liby.github.io/notes"><img src="https://avatars.githubusercontent.com/u/38807139?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bryan Lee</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=liby" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/chenmoonmo"><img src="https://avatars.githubusercontent.com/u/36295999?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Simon Li</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=chenmoonmo" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/githbq"><img src="https://avatars.githubusercontent.com/u/10009709?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Bob Hu</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=githbq" title="Code">💻</a></td>
<td align="center"><a href="https://quavo.vercel.app/"><img src="https://avatars.githubusercontent.com/u/67266933?v=4?s=50" width="50px;" alt=""/><br /><sub><b>Quavo</b></sub></a><br /><a href="https://github.com/toeverything/AFFiNE/commits?author=lucky-chap" title="Documentation">📖</a></td>
</tr>
</table>

View File

@ -0,0 +1,11 @@
{
"name": "ligo-virgo-e2e",
"version": "1.0.0",
"license": "MIT",
"description": "",
"author": "AFFiNE <developer@affine.pro>",
"dependencies": {},
"devDependencies": {
"cypress": "^10.4.0"
}
}

View File

@ -4,7 +4,7 @@ describe('ligo-virgo', () => {
beforeEach(() => cy.visit('/'));
it('basic load check', () => {
getTitle().contains('👋 Get Started with AFFINE');
getTitle().contains('👋 Get Started with AFFiNE');
cy.get('.block_container').contains('The Essentials');

View File

@ -12,14 +12,14 @@
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
login(email: string, password: string): void;
// login(email: string, password: string): void;
}
}
//
// -- This is a parent command --
Cypress.Commands.add('login', (email, password) => {
console.log('Custom command example: Login', email, password);
});
// Cypress.Commands.add('login', (email, password) => {
// console.log('Custom command example: Login', email, password);
// });
//
// -- This is a child command --
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })

View File

@ -1,11 +1,20 @@
import { Outlet } from 'react-router-dom';
import { css, Global } from '@emotion/react';
import { LayoutHeader, SettingsSidebar } from '@toeverything/components/layout';
import { styled } from '@toeverything/components/ui';
import { Outlet } from 'react-router-dom';
export function LigoVirgoRootContainer() {
return (
<StyledRootContainer id="idAppRoot">
<>
<Global
styles={css`
#root {
display: flex;
flex-direction: row;
height: 100vh;
}
`}
/>
<StyledContentContainer>
<LayoutHeader />
<StyledMainContainer>
@ -13,7 +22,7 @@ export function LigoVirgoRootContainer() {
</StyledMainContainer>
</StyledContentContainer>
<SettingsSidebar />
</StyledRootContainer>
</>
);
}
@ -23,12 +32,6 @@ const StyledMainContainer = styled('div')({
overflowY: 'hidden',
});
const StyledRootContainer = styled('div')({
display: 'flex',
flexDirection: 'row',
height: '100vh',
});
const StyledContentContainer = styled('div')({
flex: 'auto',
display: 'flex',

View File

@ -9,11 +9,11 @@ const MemoAffineBoard = memo(AffineBoard, (prev, next) => {
return prev.rootBlockId === next.rootBlockId;
});
type WhiteboardProps = {
type EdgelessProps = {
workspace: string;
};
export const Whiteboard = (props: WhiteboardProps) => {
export const Edgeless = (props: EdgelessProps) => {
const { page_id } = useParams();
const { user } = useUserAndSpaces();

View File

@ -22,8 +22,12 @@ export function WorkspaceHome() {
workspace_id,
user_initial_page_id,
TemplateFactory.generatePageTemplateByGroupKeys({
name: '👋 Get Started with AFFINE',
groupKeys: ['getStartedGroup0', 'getStartedGroup1'],
name: '👋 Get Started with AFFiNE',
groupKeys: [
'getStartedGroup0',
'getStartedGroup1',
'getStartedGroup2',
],
})
);
}

View File

@ -4,10 +4,10 @@ import { useUserAndSpaces } from '@toeverything/datasource/state';
import { WorkspaceRootContainer } from './Container';
import { Page } from './docs';
import { Edgeless } from './Edgeless';
import { WorkspaceHome } from './Home';
import Labels from './labels';
import Pages from './pages';
import { Whiteboard } from './Whiteboard';
export function WorkspaceContainer() {
const { workspace_id } = useParams();
@ -26,8 +26,8 @@ export function WorkspaceContainer() {
<Route path="/labels" element={<Labels />} />
<Route path="/pages" element={<Pages />} />
<Route
path="/:page_id/whiteboard"
element={<Whiteboard workspace={workspace_id} />}
path="/:page_id/edgeless"
element={<Edgeless workspace={workspace_id} />}
/>
<Route
path="/:page_id"

View File

@ -73,9 +73,9 @@ module.exports = function (webpackConfig) {
priority: -5,
chunks: 'all',
},
whiteboard: {
edgeless: {
test: /(libs\/components\/board-|[\\/]node_modules[\\/]@tldraw)/,
name: 'whiteboard',
name: 'edgeless',
priority: -7,
chunks: 'all',
},

View File

@ -715,9 +715,9 @@ export function App() {
>
Your data is yours; it is always locally stored and
secured - available to you always. While still being
able enjoy collaboration features such as real-time
editing and sharing with others, without any cloud
setup.
able to enjoy collaboration features such as
real-time editing and sharing with others, without
any cloud setup.
</Typography>
</Box>
</Grid>

View File

@ -1,13 +0,0 @@
# AFFiNE CONTRIBUTING
Contributions are **welcome** and will be fully **credited**.
## **Requirements**
If the project maintainer has any additional requirements, you will find them listed here.
- Code Style [AFFiNE Code Guideline](./affine-code-guideline.md)
- Git Rules [AFFiNE Git Guideline ](./affine-git-guideline.md)
- • **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
**Happy coding**!

View File

@ -1,22 +0,0 @@
# AFFiNE Code Guideline
| Item | Specification | Example |
| ----------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [Packages/Paths]() | aaa-bbb-ccc | ligo-virgo, editor-todo |
| [.tsx]() | PascalCase | AddPage.tsx |
| [.ts]() | kebab-case | file-export.ts |
| [.json]() | kebab-case | file-export.ts |
| [Domain File]() | OpenRules | xx._d.ts_ &#124; _tsconfig.xx_.json &#124; xx.spec .ts &#124; .env.xx &#124; yy-ds.ts |
| [Types]() | UpperCamelCase | WebEvent |
| [Enum variants]() | UpperCamelCase | Status{ Todo,Completed } |
| [Functions]() | lowerCamelCase | |
| [React Funciton Compoment]() | UpperCamelCase | function DocShare(){} |
| [React HOC]() | UpperCamelCase | function BussinessText(){} |
| [Function Parameter]() | lowerCamelCase | function searchByIdOrName(idOrname){ } |
| [Methods for external access]() | lowerCamelCase | public sayHello(){ }; |
| [Externally Accessible Variables (Variables)]() | lowerCamelCase | animal.sleepCount |
| [General constructors]() | constructor or with_more_details | |
| [Local variables]() | lowerCamelCase | const tableCollection = []; |
| [Statics]() | SCREAMING_SNAKE_CASE | GLOBAL_MESSAGES |
| [Constants](b) | SCREAMING_SNAKE_CASE | GLOBAL_CONFIG |
| [Type parameters]() | UpperCamelCase , usually a single capital letter: T | let a: Animal = new Animal() |

View File

@ -1,91 +0,0 @@
# AFFiNE Git Guideline
# 1. Git Branch Name
- fix/
- feat/
# 2. **Commit message guidelines**
AFFiNE uses [semantic-release](https://github.com/semantic-release/semantic-release) for automated version management and package publishing. For that to work, commitmessages need to be in the right format.
### **Atomic commits**
If possible, make [atomic commits](https://en.wikipedia.org/wiki/Atomic_commit), which means:
- a commit should contain exactly one self-contained functional change
- a functional change should be contained in exactly one commit
- a commit should not create an inconsistent state (such as test errors, linting errors, partial fix, feature with documentation etc...)
A complex feature can be broken down into multiple commits as long as each one keep a consistent state and consist of a self-contained change.
### **Commit message format**
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special format that includes a **type**, a **scope** and a **subject**:
`<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>`
The **header** is mandatory and the **scope** of the header is optional.
The **footer** can contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages).
### **Revert**
If the commit reverts a previous commit, it should begin with `revert:` , followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
### **Type**
The type must be one of the following:
| Type | Description |
| ----------- | ----------- |
| build | Changes that affect the build system or external | dependencies (example scopes: gulp, broccoli, npm) |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) |
| docs | Documentation only changes |
| feat | A new feature |
| fix | A bug fix |
| perf | A code change that improves performance |
| refactor | A code change that neither fixes a bug nor adds a feature |
| style | Changes that do not affect the meaning of the code(white-space, formatting, missing semi-colons, etc) |
| test | Adding missing tests or correcting existing tests |
| chore | Changes to the build process or auxiliary tools | |
### **Subject**
The subject contains succinct description of the change:
- use the imperative, present tense: "change" not "changed" nor "changes"
- don't capitalize first letter
- no dot (.) at the end
### **Body**
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes". The body should include the motivation for the change and contrast this with previous behavior.
### **Footer**
The footer should contain any information about **Breaking Changes** and is also the place to reference GitHub issues that this commit **Closes**.
**Breaking Changes** should start with the word `BREAKING CHANGE:` with a space or two newlines. The rest of the commit message is then used for this.
### **Examples**
`fix(pencil): stop graphite breaking when too much pressure applied`
``feat(pencil): add 'graphiteWidth' option`
Fix #42`
`perf(pencil): remove graphiteWidth option`
BREAKING CHANGE: The graphiteWidth option has been removed.
The default graphite width of 10mm is always used for performance reasons.`
# 3. tracking-your-work-with-issues
[https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues](https://docs.github.com/en/issues/tracking-your-work-with-issues/about-issues)

View File

@ -1,7 +0,0 @@
## AFFiNE Icons
> Abundant and colorful icon resource for free free use
Website: [http://localhost:4200/tools/icons](http://localhost:4200/tools/icons)
Figma: [Figma AFFiNE Icons](https://www.figma.com/file/7pyx5gMz6CN0qSRADmScQ7/AFFINE?node-id=665%3A1734)

View File

@ -1,53 +0,0 @@
# Tutorial
AFFiNE defines a new component development specification in Figma, extends AFFiNE UI Components based on MUI BASE and MUI SYSTEM, and supplements as needed https://github.com/toeverything/AFFiNE/tree/master/libs/components/ui , eg `src/libs/components/ui/src/button/base-button.ts`
```jsx
import ButtonUnstyled, {
buttonUnstyledClasses,
} from '@mui/base/ButtonUnstyled';
import { styled } from '../styled';
/* eslint-disable @typescript-eslint/naming-convention */
const blue = {
500: '#007FFF',
600: '#0072E5',
700: '#0059B2',
};
/* eslint-enable @typescript-eslint/naming-convention */
export const BaseButton = styled(ButtonUnstyled)`
font-family: IBM Plex Sans, sans-serif;
font-weight: bold;
font-size: 0.875rem;
background-color: ${blue[500]};
border-radius: 8px;
padding: 4px 8px;
color: white;
transition: all 150ms ease;
cursor: pointer;
border: none;
&:hover {
background-color: ${blue[600]};
}
&.${buttonUnstyledClasses.active} {
background-color: ${blue[700]};
}
&.${buttonUnstyledClasses.focusVisible} {
box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 5px rgba(0, 127, 255, 0.5);
outline: none;
}
&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
`;
```
```jsx
export { BaseButton } from './base-button';
```

View File

@ -1,13 +0,0 @@
Create or edit the `.env.local` file in the project root directory, add `FIGMA_TOKEN`, you can refer to Generate ACCESS_TOKEN: https://www.figma.com/developers/api#access-tokens
```
FIGMA_TOKEN=your-figma-token
```
Execute the command `nx run components-icons:figmaRes` to synchronize figma resources
figma icon resource address: https://www.figma.com/file/7pyx5gMz6CN0qSRADmScQ7/AFFINE?node-id=665%3A1734
### Icon Supplementary Style
Some icons downloaded directly have incorrect styles. You can add supplementary styles in `tools/executors/figmaRes/patch-styles.js`. The key is the name of the icon kebab-case.

View File

@ -1,3 +0,0 @@
## Single Component Rollup Config
If styles are used in a Component, `rollupConfig` under `project.json` needs to be modified to `libs/rollup.config.cjs`

View File

@ -1,103 +0,0 @@
[toc]
# Tutorial
1. MUI styled
```jsx
import type { MouseEventHandler, ReactNode } from 'react';
import { styled } from '@toeverything/components/ui';
const CardContainer = styled('div')({
display: 'flex',
flexDirection: 'column',
backgroundColor: '#fff',
border: '1px solid #E2E7ED',
borderRadius: '5px',
});
const CardContent = styled('div')({
margin: '23px 52px 24px 19px',
});
const CardActions = styled('div')({
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
width: '100%',
height: '29px',
background: 'rgba(152, 172, 189, 0.1)',
borderRadius: '0px 0px 5px 5px',
padding: '6px 0 6px 19px',
fontSize: '12px',
fontWeight: '300',
color: '#98ACBD',
});
const PlusIcon = styled('div')({
marginRight: '9px',
fontWeight: '500',
lineHeight: 0,
'::before': {
content: '"+"',
},
});
export const Card = ({
children,
onAddItem,
}: {
children?: ReactNode,
onAddItem?: MouseEventHandler<HTMLDivElement>,
}) => {
return (
<CardContainer>
<CardContent>{children}</CardContent>
<CardActions onClick={onAddItem}>
<PlusIcon />
Add item
</CardActions>
</CardContainer>
);
};
```
## 2. import `*.scss`
```jsx
import styles from './tree-item.module.scss';
export const TreeItem = forwardRef<HTMLDivElement, TreeItemProps>(
() => {
return (
<li
ref={wrapperRef}
className={cx(
styles['Wrapper'],
clone && styles['clone'],
ghost && styles['ghost'],
indicator && styles['indicator']
)}
style={
{
'--spacing': `${indentationWidth * depth}px`
} as CSSProperties
}
{...props}
>
</li>
);
}
);
```
## 3. import `*.css`,
```js
import 'codemirror/lib/codemirror.css';
import 'codemirror/lib/codemirror';
```

View File

@ -17,7 +17,7 @@ interface AffineEditorProps {
*/
scrollBlank?: boolean;
isWhiteboard?: boolean;
isEdgeless?: boolean;
scrollContainer?: HTMLElement;
scrollController?: {
@ -29,12 +29,12 @@ interface AffineEditorProps {
function _useConstantWithDispose(
workspace: string,
rootBlockId: string,
isWhiteboard: boolean
isEdgeless: boolean
) {
const ref = useRef<{ data: BlockEditor; onInit: boolean }>(null);
const { setCurrentEditors } = useCurrentEditors();
ref.current ??= {
data: createEditor(workspace, rootBlockId, isWhiteboard),
data: createEditor(workspace, rootBlockId, isEdgeless),
onInit: true,
};
@ -42,18 +42,14 @@ function _useConstantWithDispose(
if (ref.current.onInit) {
ref.current.onInit = false;
} else {
ref.current.data = createEditor(
workspace,
rootBlockId,
isWhiteboard
);
ref.current.data = createEditor(workspace, rootBlockId, isEdgeless);
}
setCurrentEditors(prev => ({
...prev,
[rootBlockId]: ref.current.data,
}));
return () => ref.current.data.dispose();
}, [workspace, rootBlockId, isWhiteboard, setCurrentEditors]);
}, [workspace, rootBlockId, isEdgeless, setCurrentEditors]);
return ref.current.data;
}
@ -64,7 +60,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
workspace,
rootBlockId,
scrollBlank = true,
isWhiteboard,
isEdgeless,
scrollController,
scrollContainer,
},
@ -73,7 +69,7 @@ export const AffineEditor = forwardRef<BlockEditor, AffineEditorProps>(
const editor = _useConstantWithDispose(
workspace,
rootBlockId,
isWhiteboard
isEdgeless
);
useEffect(() => {

View File

@ -30,7 +30,7 @@ import { BlockEditor } from '@toeverything/framework/virgo';
export const createEditor = (
workspace: string,
rootBlockId: string,
isWhiteboard?: boolean
isEdgeless?: boolean
) => {
const blockEditor = new BlockEditor({
workspace,
@ -61,7 +61,7 @@ export const createEditor = (
[Protocol.Block.Type.groupDivider]: new GroupDividerBlock(),
},
plugins,
isWhiteboard,
isEdgeless,
});
return blockEditor;

View File

@ -100,6 +100,7 @@ export function groupShapes(
afterShapes[groupId] = TLDR.get_shape_util(TDShapeType.Group).create({
id: groupId,
affineId: groupId,
childIndex: groupChildIndex,
parentId: groupParentId,
point: [groupBounds.minX, groupBounds.minY],
@ -217,7 +218,6 @@ export function groupShapes(
}
}
});
return {
id: 'group',
before: {

View File

@ -0,0 +1,66 @@
import { styled, Tooltip } from '@toeverything/components/ui';
import { AlignType } from '../command-panel/AlignOperation';
interface AlignObject {
name?: string;
/**
* color: none means no color
*/
title: string;
icon?: JSX.Element;
}
/**
* ColorValue : none means no color
*/
interface AlignProps {
alignOptions: AlignObject[];
selected?: string;
onSelect?: (alginType: AlignType) => void;
}
export const AlignPanel = ({
alignOptions,
selected,
onSelect,
}: AlignProps) => {
return (
<Container>
{alignOptions.map(alignOption => {
const option = alignOption.name as AlignType;
// const selected = color;
return (
<Tooltip key={option} content={alignOption.title}>
<SelectableContainer
onClick={() => {
onSelect?.(option);
}}
>
{alignOption.icon}
</SelectableContainer>
</Tooltip>
);
})}
</Container>
);
};
const Container = styled('div')({
width: '170px',
display: 'flex',
flexWrap: 'wrap',
});
const SelectableContainer = styled('div')<{ selected?: boolean }>(
({ selected, theme }) => ({
color: theme.affine.palette.icons,
borderRadius: '5px',
overflow: 'hidden',
margin: '5px',
padding: '3px',
cursor: 'pointer',
boxSizing: 'border-box',
'&:hover': {
backgroundColor: theme.affine.palette.hover,
},
})
);

View File

@ -0,0 +1,108 @@
import type { TldrawApp } from '@toeverything/components/board-state';
import { DistributeType, TDShape } from '@toeverything/components/board-types';
import {
AlignIcon,
ShapesAlignBottomIcon,
ShapesAlignHorizontalCenterIcon,
ShapesAlignLeftIcon,
ShapesAlignRightIcon,
ShapesAlignTopIcon,
ShapesAlignVerticalCenterIcon,
ShapesDistributeHorizontalIcon,
ShapesDistributeVerticalIcon,
} from '@toeverything/components/icons';
import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
import { AlignPanel } from '../align-panel';
interface BorderColorConfigProps {
app: TldrawApp;
shapes: TDShape[];
}
export enum AlignType {
Top = 'top',
CenterVertical = 'centerVertical',
Bottom = 'bottom',
Left = 'left',
CenterHorizontal = 'centerHorizontal',
Right = 'right',
}
let AlignPanelArr = [
{
name: 'left',
title: 'Align left',
icon: <ShapesAlignLeftIcon></ShapesAlignLeftIcon>,
},
{
name: 'centerVertical',
title: 'Align Center Vertical',
icon: <ShapesAlignVerticalCenterIcon></ShapesAlignVerticalCenterIcon>,
},
{
name: 'right',
title: 'Align right',
icon: <ShapesAlignRightIcon></ShapesAlignRightIcon>,
},
{
name: 'top',
title: 'Align top',
icon: <ShapesAlignTopIcon></ShapesAlignTopIcon>,
},
{
name: 'bottom',
title: 'Align bottom',
icon: <ShapesAlignBottomIcon></ShapesAlignBottomIcon>,
},
{
name: 'centerHorizontal',
title: 'Align centerHorizontal',
icon: (
<ShapesAlignHorizontalCenterIcon></ShapesAlignHorizontalCenterIcon>
),
},
{
name: 'distributeCenterHorizontal',
title: 'Align distribute center horizontal',
icon: <ShapesDistributeHorizontalIcon></ShapesDistributeHorizontalIcon>,
},
{
name: 'distributeCenterVertical',
title: 'Align distribute center horizontal',
icon: <ShapesDistributeVerticalIcon></ShapesDistributeVerticalIcon>,
},
];
export const AlignOperation = ({ app, shapes }: BorderColorConfigProps) => {
const setAlign = (alginType: string) => {
switch (alginType) {
case 'distributeCenterHorizontal':
app.distribute(DistributeType.Horizontal);
break;
case 'distributeCenterVertical':
app.distribute(DistributeType.Vertical);
break;
default:
app.align(alginType as AlignType);
break;
}
};
return (
<Popover
trigger="hover"
placement="bottom-start"
content={
<AlignPanel
alignOptions={AlignPanelArr}
onSelect={setAlign}
></AlignPanel>
}
>
<Tooltip content="Align" placement="top-start">
<IconButton>
<AlignIcon />
</IconButton>
</Tooltip>
</Popover>
);
};

View File

@ -1,6 +1,7 @@
import { TLDR, TldrawApp } from '@toeverything/components/board-state';
import { Divider, Popover, styled } from '@toeverything/components/ui';
import { Fragment } from 'react';
import { AlignOperation } from './AlignOperation';
import { BorderColorConfig } from './BorderColorConfig';
import { DeleteShapes } from './DeleteOperation';
import { FillColorConfig } from './FillColorConfig';
@ -8,6 +9,7 @@ import { FontSizeConfig } from './FontSizeConfig';
import { FrameFillColorConfig } from './FrameFillColorConfig';
import { Group, UnGroup } from './GroupOperation';
import { Lock, Unlock } from './LockOperation';
import { MoveCoverageConfig } from './MoveCoverage';
import { StrokeLineStyleConfig } from './stroke-line-style-config';
import { getAnchor, useConfig } from './utils';
@ -91,6 +93,19 @@ export const CommandPanel = ({ app }: { app: TldrawApp }) => {
shapes={config.deleteShapes.selectedShapes}
/>
),
moveCoverageConfig: (
<MoveCoverageConfig
key="deleteShapes1"
app={app}
shapes={config.deleteShapes.selectedShapes}
/>
),
alginOperation: config.group.selectedShapes.length ? (
<AlignOperation
app={app}
shapes={config.deleteShapes.selectedShapes}
></AlignOperation>
) : null,
};
const nodes = Object.entries(configNodes).filter(([key, node]) => !!node);

View File

@ -1,7 +1,8 @@
import type { TldrawApp } from '@toeverything/components/board-state';
import type { TDShape } from '@toeverything/components/board-types';
import { TLDR, TldrawApp } from '@toeverything/components/board-state';
import { TDShape, TDShapeType } from '@toeverything/components/board-types';
import { GroupIcon, UngroupIcon } from '@toeverything/components/icons';
import { IconButton, Tooltip } from '@toeverything/components/ui';
import { services } from '@toeverything/datasource/db-service';
import { getShapeIds } from './utils';
interface GroupAndUnGroupProps {
@ -10,8 +11,38 @@ interface GroupAndUnGroupProps {
}
export const Group = ({ app, shapes }: GroupAndUnGroupProps) => {
const group = () => {
app.group(getShapeIds(shapes));
const group = async () => {
let groupShape = await services.api.editorBlock.create({
workspace: app.document.id,
parentId: app.appState.currentPageId,
type: 'shape',
});
await services.api.editorBlock.update({
workspace: groupShape.workspace,
id: groupShape.id,
properties: {
shapeProps: {
value: JSON.stringify(
TLDR.get_shape_util(TDShapeType.Group).create({
id: groupShape.id,
affineId: groupShape.id,
childIndex: 1,
parentId: app.appState.currentPageId,
point: [0, 0],
size: [0, 0],
children: getShapeIds(shapes),
workspace: app.document.id,
})
),
},
},
});
app.group(
getShapeIds(shapes),
groupShape.id,
app.appState.currentPageId
);
};
return (
<Tooltip content="Group">

View File

@ -0,0 +1,77 @@
import type { TldrawApp } from '@toeverything/components/board-state';
import type { TDShape } from '@toeverything/components/board-types';
import {
BringForwardIcon,
BringToFrontIcon,
LayersIcon,
SendBackwardIcon,
SendToBackIcon,
} from '@toeverything/components/icons';
import { IconButton, Popover, Tooltip } from '@toeverything/components/ui';
import { AlignPanel } from '../align-panel';
interface FontSizeConfigProps {
app: TldrawApp;
shapes: TDShape[];
}
const AlignPanelArr = [
{
title: 'To Front',
name: 'tofront',
icon: <BringToFrontIcon />,
},
{
title: 'Forward',
name: 'forward',
icon: <BringForwardIcon />,
},
{
title: 'Backward',
name: 'backward',
icon: <SendBackwardIcon />,
},
{
title: 'To Back',
name: 'toback',
icon: <SendToBackIcon />,
},
];
export const MoveCoverageConfig = ({ app, shapes }: FontSizeConfigProps) => {
const moveCoverage = (type: string) => {
switch (type) {
case 'toback':
app.moveToBack();
break;
case 'backward':
app.moveBackward();
break;
case 'forward':
app.moveForward();
break;
case 'tofront':
app.moveToFront();
break;
}
};
return (
<Popover
trigger="hover"
placement="bottom-start"
content={
<AlignPanel
alignOptions={AlignPanelArr}
onSelect={moveCoverage}
></AlignPanel>
}
>
<Tooltip content="Layers" placement="top-start">
<IconButton>
<LayersIcon />
</IconButton>
</Tooltip>
</Popover>
);
};

View File

@ -52,8 +52,8 @@ const tools: Array<{
{ type: 'frame', label: 'Frame', tooltip: 'Frame', icon: FrameIcon },
{
type: TDShapeType.Editor,
label: 'Text',
tooltip: 'Text',
label: 'Text Block',
tooltip: 'Text Block',
icon: TextIcon,
},
{ type: 'shapes', component: ShapeTools },
@ -61,9 +61,9 @@ const tools: Array<{
{ type: 'Connector', component: LineTools },
// { type: 'erase', label: 'Erase', tooltip: 'Erase', icon: EraseIcon },
{
type: TDShapeType.HandDraw,
label: 'HandDraw',
tooltip: 'HandDraw',
type: TDShapeType.HandDrag,
label: 'Hand Drag',
tooltip: 'Hand Drag',
icon: HandToolIcon,
},
{

View File

@ -111,7 +111,8 @@ export class TranslateSession extends BaseSession {
Utils.boundsContain(
TLDR.get_bounds(shap),
TLDR.get_bounds(shapItem)
)
) &&
!shapItem.isLocked
) {
selectedShapes.push(shapItem);
}

View File

@ -10,7 +10,7 @@ import {
} from '@toeverything/components/board-types';
import { MIN_PAGE_WIDTH } from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import type { SyntheticEvent } from 'react';
import type { MouseEvent, SyntheticEvent } from 'react';
import { memo, useCallback, useEffect, useRef } from 'react';
import {
defaultTextStyle,
@ -135,6 +135,15 @@ export class EditorUtil extends TDShapeUtil<T, E> {
}
}, [app, state, shape.id, editingText, editingId]);
const onMouseDown = useCallback(
(e: MouseEvent) => {
if (e.detail === 2) {
app.setEditingText(shape.id);
}
},
[app, shape.id]
);
return (
<HTMLContainer ref={ref} {...events}>
<Container
@ -143,12 +152,13 @@ export class EditorUtil extends TDShapeUtil<T, E> {
onPointerDown={stopPropagation}
onMouseEnter={activateIfEditing}
onDragEnter={activateIfEditing}
onMouseDown={onMouseDown}
>
<MemoAffineEditor
workspace={workspace}
rootBlockId={rootBlockId}
scrollBlank={false}
isWhiteboard
isEdgeless
/>
{editingText ? null : <Mask />}
</Container>

View File

@ -3853,12 +3853,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
};
private get_viewbox_from_svg = (svgStr: string | ArrayBuffer | null) => {
const viewBoxRegex =
/.*?viewBox=["'](-?[\d.]+[, ]+-?[\d.]+[, ][\d.]+[, ][\d.]+)["']/;
if (typeof svgStr === 'string') {
const matches = svgStr.match(viewBoxRegex);
return matches && matches.length >= 2 ? matches[1] : null;
let viewBox = new DOMParser().parseFromString(svgStr, 'text/xml');
return viewBox.children[0].getAttribute('viewBox');
}
console.warn('could not get viewbox from svg string');
@ -3994,7 +3991,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
this.patchState({
settings: {
forcePanning:
this.currentTool.type === TDShapeType.HandDraw,
this.currentTool.type === TDShapeType.HandDrag,
},
});
this.spaceKey = false;

View File

@ -9,8 +9,8 @@ enum Status {
Draw = 'draw',
}
export class HandDrawTool extends BaseTool {
override type = TDShapeType.HandDraw as const;
export class HandDragTool extends BaseTool {
override type = TDShapeType.HandDrag as const;
override status: Status = Status.Idle;

View File

@ -0,0 +1 @@
export * from './hand-drag-tool';

View File

@ -1 +0,0 @@
export * from './hand-draw-tool';

View File

@ -5,7 +5,7 @@ import { EditorTool } from './editor-tool';
import { EllipseTool } from './ellipse-tool';
import { EraseTool } from './erase-tool';
import { FrameTool } from './frame-tool/frame-tool';
import { HandDrawTool } from './hand-draw';
import { HandDragTool } from './hand-drag';
import { HexagonTool } from './hexagon-tool';
import { HighlightTool } from './highlight-tool';
import { LaserTool } from './laser-tool';
@ -32,7 +32,7 @@ export interface ToolsMap {
[TDShapeType.Highlight]: typeof HighlightTool;
[TDShapeType.Editor]: typeof EditorTool;
[TDShapeType.WhiteArrow]: typeof WhiteArrowTool;
[TDShapeType.HandDraw]: typeof HandDrawTool;
[TDShapeType.HandDrag]: typeof HandDragTool;
[TDShapeType.Laser]: typeof LaserTool;
[TDShapeType.Frame]: typeof FrameTool;
}
@ -59,6 +59,6 @@ export const tools: { [K in TDToolType]: ToolsMap[K] } = {
[TDShapeType.Hexagon]: HexagonTool,
[TDShapeType.WhiteArrow]: WhiteArrowTool,
[TDShapeType.Laser]: LaserTool,
[TDShapeType.HandDraw]: HandDrawTool,
[TDShapeType.HandDrag]: HandDragTool,
[TDShapeType.Frame]: FrameTool,
};

View File

@ -212,7 +212,7 @@ export type TDToolType =
| TDShapeType.WhiteArrow
| TDShapeType.Editor
| TDShapeType.Frame
| TDShapeType.HandDraw;
| TDShapeType.HandDrag;
export type Easing =
| 'linear'
@ -287,7 +287,7 @@ export enum TDShapeType {
Video = 'video',
Editor = 'editor',
WhiteArrow = 'white-arrow',
HandDraw = 'hand-draw',
HandDrag = 'hand-drag',
Frame = 'frame',
}

View File

@ -2,28 +2,28 @@
import {
Descendant,
Editor,
Location,
Node as SlateNode,
Path,
Point,
Transforms,
Range,
Text,
Location,
Path,
Node as SlateNode,
Transforms,
} from 'slate';
import { ReactEditor } from 'slate-react';
import {
getCommentsIdsOnTextNode,
getEditorMarkForCommentId,
MARKDOWN_STYLE_MAP,
MatchRes,
} from './utils';
import {
fontBgColorPalette,
fontColorPalette,
type TextAlignOptions,
type TextStyleMark,
} from './constants';
import {
getCommentsIdsOnTextNode,
getEditorMarkForCommentId,
MARKDOWN_STYLE_MAP,
MatchRes,
} from './utils';
function isInlineAndVoid(editor: Editor, el: any) {
return editor.isInline(el) && editor.isVoid(el);
@ -567,8 +567,51 @@ class SlateUtils {
anchor: point1,
focus: point2,
});
// @ts-ignore
return fragment[0].children;
if (!fragment.length) {
console.error('Debug information:', point1, point2, fragment);
throw new Error('Failed to get content between!');
}
if (fragment.length > 1) {
console.warn(
'Fragment length is greater than one, ' +
'please be careful if there is missing content!\n' +
'Debug information:',
point1,
point2,
fragment
);
}
const firstFragment = fragment[0];
if (!('type' in firstFragment)) {
console.error('Debug information:', point1, point2, fragment);
throw new Error(
'Failed to get content between! type of firstFragment not found!'
);
}
const fragmentChildren = firstFragment.children;
const textChildren: Text[] = [];
for (const child of fragmentChildren) {
if (!('text' in child)) {
console.error('Debug information:', point1, point2, fragment);
throw new Error('Fragment exists nested!');
}
// Filter empty string
if (child.text === '') {
continue;
}
textChildren.push(child);
}
// If nothing, should preserve empty string
// Fix Slate Cannot get the start point in the node at path [0] because it has no start text node.
if (!textChildren.length) {
textChildren.push({ text: '' });
}
return textChildren;
}
public getSplitContentsBySelection() {

View File

@ -173,7 +173,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
}
return true;
} else {
return tabBlock(block, isShiftKey);
return tabBlock(editor, block, isShiftKey);
}
};

View File

@ -1,9 +1,9 @@
import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
SelectBlock,
} from '@toeverything/framework/virgo';
import { Protocol, services } from '@toeverything/datasource/db-service';
import { FigmaView } from './FigmaView';
export class FigmaBlock extends BaseView {
@ -19,7 +19,10 @@ export class FigmaBlock extends BaseView {
const tag_name = el.tagName;
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
const href = el.getAttribute('href');
if (href.indexOf('.figma.com') !== -1) {
const allowedHosts = ['www.figma.com'];
const host = new URL(href).host;
if (allowedHosts.includes(host)) {
return [
{
type: this.type,

View File

@ -103,7 +103,7 @@ export const GroupView = (props: CreateView) => {
<View {...props} />
</GroupContainer>
{editor.isWhiteboard ? null : (
{editor.isEdgeless ? null : (
<GroupAction onAddGroup={addGroup} />
)}
</GroupBox>

View File

@ -49,7 +49,7 @@ const weakSqlCreator = (weak_sql_express = ''): Promise<Constraint[]> => {
constraints.push({
field: field.trim(),
relation: relation.trim() as Relation,
value: pickValue(value.replace(/&&|&|;/, '').trim()),
value: pickValue(value.replace(/&&|&|;/g, '').trim()),
});
/* meaningless return value */

View File

@ -167,7 +167,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
}
return true;
} else {
tabBlock(block, isShiftKey);
tabBlock(editor, block, isShiftKey);
return false;
}
};

View File

@ -2,13 +2,11 @@ import { useState } from 'react';
import { CustomText, TextProps } from '@toeverything/components/common';
import {
mergeGroup,
BlockPendantProvider,
RenderBlockChildren,
splitGroup,
supportChildren,
unwrapGroup,
useOnSelect,
BlockPendantProvider,
} from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { Protocol } from '@toeverything/datasource/db-service';
@ -16,7 +14,7 @@ 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';
import { dedentBlock, tabBlock } from '../../utils/indent';
interface CreateTextView extends CreateView {
// TODO: need to optimize
containerClassName?: string;
@ -69,24 +67,36 @@ export const TextView = ({
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', {
const nextBlockChildren = await block.children();
const nextBlock = await editor.createBlock('text');
if (!nextBlock) {
throw new Error('Failed to create text block');
}
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);
if (editor.getRootBlockId() === block.id) {
// If the block is the root block,
// new block can not append as next sibling,
// all new blocks should be append as children.
await block.insert(0, [nextBlock]);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
}
await nextBlock.append(...nextBlockChildren);
await block.removeChildren();
await block.after(nextBlock);
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
return true;
};
const onBackspace: TextProps['handleBackSpace'] = async props => {
return await editor.withSuspend(async () => {
const onBackspace: TextProps['handleBackSpace'] = editor.withBatch(
async props => {
const { isCollAndStart } = props;
if (!isCollAndStart) {
return false;
@ -95,28 +105,44 @@ export const TextView = ({
await block.setType('text');
return true;
}
if (editor.getRootBlockId() === block.id) {
// Can not delete
return false;
}
const parentBlock = await block.parent();
if (!parentBlock) {
return false;
}
const preParent = await parentBlock.previousSibling();
if (Protocol.Block.Type.group === parentBlock.type) {
// The parent block is group block or is the root block.
// Merge block to previous sibling.
//
// - group/root <- parent block
// - text1 <- preNode
// - text2 <- press backspace before target block
// - children
//
// ---
//
// - group/root
// - text1text2 <- merge block to previous sibling
// - children <- children should switch parent block
if (
Protocol.Block.Type.group === parentBlock.type ||
editor.getRootBlockId() === parentBlock.id
) {
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'
);
if (
block.getProperty('text').value[0] &&
block.getProperty('text').value[0]?.text !== ''
) {
if (!block.blockProvider?.isEmpty()) {
const value = [
...preNode.getProperty('text').value,
...block.getProperty('text').value,
@ -127,14 +153,14 @@ export const TextView = ({
}
await preNode.append(...children);
await block.remove();
editor.suspend(false);
} else {
// If not pre node, block should be merged to the parent block
// TODO: point does not clear
await editor.selectionManager.activePreviousNode(
block.id,
'start'
);
if (block.blockProvider.isEmpty()) {
if (block.blockProvider?.isEmpty()) {
await block.remove();
const parentChild = await parentBlock.children();
if (
@ -142,6 +168,8 @@ export const TextView = ({
Protocol.Block.Type.group &&
!parentChild.length
) {
const preParent =
await parentBlock.previousSibling();
await editor.selectionManager.setSelectedNodesIds(
[preParent?.id ?? editor.getRootBlockId()]
);
@ -182,20 +210,13 @@ export const TextView = ({
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
);
}
dedentBlock(editor, block);
return true;
});
};
}
);
const handleConvert = async (
toType: string,
options?: Record<string, unknown>
@ -211,7 +232,7 @@ export const TextView = ({
block.firstCreateFlag = true;
};
const onTab: TextProps['handleTab'] = async ({ isShiftKey }) => {
await tabBlock(block, isShiftKey);
await tabBlock(editor, block, isShiftKey);
return true;
};

View File

@ -100,7 +100,7 @@ export const TodoView = ({ block, editor }: CreateView) => {
};
const on_tab: TextProps['handleTab'] = async ({ isShiftKey }) => {
await tabBlock(block, isShiftKey);
await tabBlock(editor, block, isShiftKey);
return true;
};

View File

@ -1,9 +1,9 @@
import { Protocol } from '@toeverything/datasource/db-service';
import {
AsyncBlock,
BaseView,
SelectBlock,
} from '@toeverything/framework/virgo';
import { Protocol } from '@toeverything/datasource/db-service';
import { YoutubeView } from './YoutubeView';
export class YoutubeBlock extends BaseView {
@ -19,7 +19,10 @@ export class YoutubeBlock extends BaseView {
const tag_name = el.tagName;
if (tag_name === 'A' && el.parentElement?.childElementCount === 1) {
const href = el.getAttribute('href');
if (href.indexOf('.youtube.com') !== -1) {
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
const host = new URL(href).host;
if (allowedHosts.includes(host)) {
return [
{
type: this.type,

View File

@ -1,5 +1,5 @@
const _regex =
/^(https?:\/\/(localhost:4200|(nightly|app)\.affine\.pro|.*?\.ligo-virgo\.pages\.dev)\/\w{28}\/)?(affine\w{16})(\/whiteboard)?$/;
/^(https?:\/\/(localhost:4200|(nightly|app|livedemo)\.affine\.pro|.*?\.ligo-virgo\.pages\.dev)\/(\w{28}|AFFiNE)\/)?(affine[\w\-_]{16})(\/edgeless)?$/;
export const isAffineUrl = (url?: string) => {
if (!url) return false;
@ -7,5 +7,5 @@ export const isAffineUrl = (url?: string) => {
};
export const toAffineEmbedUrl = (url: string) => {
return _regex.exec(url)?.[4];
return _regex.exec(url)?.[5];
};

View File

@ -1,5 +1,7 @@
export const isYoutubeUrl = (url?: string): boolean => {
return url.includes('youtu.be') || url.includes('youtube.com');
const allowedHosts = ['www.youtu.be', 'www.youtube.com'];
const host = new URL(url).host;
return allowedHosts.includes(host);
};
const _regexp = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=)([^#&?]*).*/;

View File

@ -4,10 +4,7 @@ import {
type SlateUtils,
type TextProps,
} from '@toeverything/components/common';
import {
useOnSelectActive,
useOnSelectSetSelection,
} from '@toeverything/components/editor-core';
import { useOnSelectActive } from '@toeverything/components/editor-core';
import { styled } from '@toeverything/components/ui';
import { ContentColumnValue } from '@toeverything/datasource/db-service';
import {
@ -119,15 +116,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
const properties = block.getProperties();
const onTextViewSetSelection = (selection: Range | Point) => {
if (selection instanceof Point) {
//do some thing
} else {
textRef.current.setSelection(selection);
}
};
// block = await editor.commands.blockCommands.createNextBlock(block.id,)
const onTextViewActive = useCallback(
(point: CursorTypes) => {
// TODO code to be optimized
@ -209,7 +197,6 @@ export const TextManage = forwardRef<ExtendedTextUtils, CreateTextView>(
);
useOnSelectActive(block.id, onTextViewActive);
useOnSelectSetSelection<'Range'>(block.id, onTextViewSetSelection);
useEffect(() => {
if (textRef.current) {

View File

@ -1,4 +1,7 @@
import { supportChildren } from '@toeverything/components/editor-core';
import {
supportChildren,
type BlockEditor,
} from '@toeverything/components/editor-core';
import { Protocol } from '@toeverything/datasource/db-service';
import { AsyncBlock } from '@toeverything/framework/virgo';
import type { TodoAsyncBlock } from '../blocks/todo/types';
@ -6,14 +9,19 @@ import type { TodoAsyncBlock } from '../blocks/todo/types';
/**
* Is the block in top level
*/
export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
export const isTopLevelBlock = (
editor: BlockEditor,
block: AsyncBlock
): boolean => {
return (
parentBlock.type === Protocol.Block.Type.group ||
parentBlock.type === Protocol.Block.Type.page
editor.getRootBlockId() === block.id ||
block.type === Protocol.Block.Type.group ||
block.type === Protocol.Block.Type.page
);
};
/**
* Move down
* @returns true if indent is success
* @example
* ```
@ -31,7 +39,6 @@ export const isTopLevelBlock = (parentBlock: AsyncBlock): boolean => {
* ```
*/
const indentBlock = async (block: TodoAsyncBlock) => {
// Move down
const previousBlock = await block.previousSibling();
if (!previousBlock || !supportChildren(previousBlock)) {
@ -57,6 +64,7 @@ const indentBlock = async (block: TodoAsyncBlock) => {
};
/**
* Move up
* @returns true if dedent is success
* @example
* ```
@ -73,13 +81,15 @@ const indentBlock = async (block: TodoAsyncBlock) => {
* [ ]
* ```
*/
const dedentBlock = async (block: AsyncBlock) => {
// Move up
export const dedentBlock = async (editor: BlockEditor, block: AsyncBlock) => {
if (editor.getRootBlockId() === block.id) {
return false;
}
let parentBlock = await block.parent();
if (!parentBlock) {
throw new Error('Failed to dedent block! Parent block not found!');
}
if (isTopLevelBlock(parentBlock)) {
if (isTopLevelBlock(editor, parentBlock)) {
// Top, do nothing
return false;
}
@ -111,9 +121,13 @@ const dedentBlock = async (block: AsyncBlock) => {
return true;
};
export const tabBlock = async (block: AsyncBlock, isShiftKey: boolean) => {
export const tabBlock = async (
editor: BlockEditor,
block: AsyncBlock,
isShiftKey: boolean
) => {
if (isShiftKey) {
return await dedentBlock(block);
return await dedentBlock(editor, block);
} else {
return await indentBlock(block);
}

View File

@ -1,16 +1,16 @@
import type { BlockEditor } from './editor';
import { styled, usePatchNodes } from '@toeverything/components/ui';
import type { PropsWithChildren } from 'react';
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { EditorProvider } from './Contexts';
import { SelectionRect, SelectionRef } from './Selection';
import {
Protocol,
services,
type ReturnUnobserve,
} from '@toeverything/datasource/db-service';
import { addNewGroup, appendNewGroup } from './recast-block';
import type { PropsWithChildren } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { EditorProvider } from './Contexts';
import type { BlockEditor } from './editor';
import { useIsOnDrag } from './hooks';
import { addNewGroup, appendNewGroup } from './recast-block';
import { SelectionRect, SelectionRef } from './Selection';
interface RenderRootProps {
editor: BlockEditor;
@ -160,7 +160,7 @@ export const RenderRoot = ({
return (
<EditorProvider value={{ editor, editorElement }}>
<Container
isWhiteboard={editor.isWhiteboard}
isEdgeless={editor.isEdgeless}
ref={ref => {
if (ref != null && ref !== editor.container) {
editor.container = ref;
@ -188,7 +188,7 @@ export const RenderRoot = ({
</Content>
{/** TODO: remove selectionManager insert */}
{editor && <SelectionRect ref={selectionRef} editor={editor} />}
{editor.isWhiteboard ? null : <ScrollBlank editor={editor} />}
{editor.isEdgeless ? null : <ScrollBlank editor={editor} />}
{patchedNodes}
</Container>
</EditorProvider>
@ -262,16 +262,10 @@ function ScrollBlank({ editor }: { editor: BlockEditor }) {
const PADDING_X = 150;
const Container = styled('div')(
({
isWhiteboard,
isOnDrag,
}: {
isWhiteboard: boolean;
isOnDrag: boolean;
}) => ({
({ isEdgeless, isOnDrag }: { isEdgeless: boolean; isOnDrag: boolean }) => ({
width: '100%',
padding: isWhiteboard ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
minWidth: isWhiteboard ? 'unset' : '940px',
padding: isEdgeless ? 0 : `72px ${PADDING_X}px 0 ${PADDING_X}px`,
minWidth: isEdgeless ? 'unset' : '940px',
position: 'relative',
...(isOnDrag && {
cursor: 'grabbing',

View File

@ -1,4 +1,5 @@
/* eslint-disable max-lines */
import { Protocol } from '@toeverything/datasource/db-service';
import { domToRect, Point } from '@toeverything/utils';
import { AsyncBlock } from '../..';
import { GridDropType } from '../commands/types';
@ -6,7 +7,6 @@ import { Editor } from '../editor';
import { BlockDropPlacement, GroupDirection } from '../types';
// TODO: Evaluate implementing custom events with Rxjs
import EventEmitter from 'eventemitter3';
import { Protocol } from '@toeverything/datasource/db-service';
enum DragType {
dragBlock = 'dragBlock',
@ -281,6 +281,10 @@ export class DragDropManager {
this._editor.getRootBlockId()
);
let direction = BlockDropPlacement.none;
if (!rootBlock || !rootBlock.dom) {
console.warn('Can not find dom bind with block', rootBlock);
return;
}
const rootBlockRect = domToRect(rootBlock.dom);
let targetBlock: AsyncBlock | undefined;
let typesInfo = {
@ -303,6 +307,10 @@ export class DragDropManager {
if (direction !== BlockDropPlacement.none) {
const blockList = await this._editor.getBlockListByLevelOrder();
targetBlock = blockList.find(block => {
if (!block.dom) {
console.warn('Can not find dom bind with block', block);
return false;
}
const domRect = domToRect(block.dom);
const pointChecker =
direction === BlockDropPlacement.outerLeft

View File

@ -1,55 +1,51 @@
/* eslint-disable max-lines */
import HotKeys from 'hotkeys-js';
import LRUCache from 'lru-cache';
import { services } from '@toeverything/datasource/db-service';
import type { PatchNode } from '@toeverything/components/ui';
import type {
BlockFlavors,
ReturnEditorBlock,
UpdateEditorBlock,
} from '@toeverything/datasource/db-service';
import type { PatchNode } from '@toeverything/components/ui';
import { services } from '@toeverything/datasource/db-service';
import { AsyncBlock } from './block';
import type { WorkspaceAndBlockId } from './block';
import type { BaseView } from './views/base-view';
import { SelectionManager } from './selection';
import { Hooks, PluginManager } from './plugin';
import { EditorCommands } from './commands';
import {
Virgo,
HooksRunner,
PluginHooks,
PluginCreator,
StorageManager,
VirgoSelection,
PluginManagerInterface,
} from './types';
import { KeyboardManager } from './keyboard';
import { MouseManager } from './mouse';
import { ScrollManager } from './scroll';
import assert from 'assert';
import { domToRect, last, Point, sleep } from '@toeverything/utils';
import { Commands } from '@toeverything/datasource/commands';
import { domToRect, last, Point, sleep } from '@toeverything/utils';
import assert from 'assert';
import type { WorkspaceAndBlockId } from './block';
import { AsyncBlock } from './block';
import { BlockHelper } from './block/block-helper';
import { BrowserClipboard } from './clipboard/browser-clipboard';
import { ClipboardPopulator } from './clipboard/clipboard-populator';
import { BlockHelper } from './block/block-helper';
import { DragDropManager } from './drag-drop';
import { EditorCommands } from './commands';
import { EditorConfig } from './config';
import { DragDropManager } from './drag-drop';
import { KeyboardManager } from './keyboard';
import { MouseManager } from './mouse';
import { Hooks, PluginManager } from './plugin';
import { ScrollManager } from './scroll';
import { SelectionManager } from './selection';
import {
HooksRunner,
PluginCreator,
PluginHooks,
PluginManagerInterface,
StorageManager,
Virgo,
VirgoSelection,
} from './types';
import type { BaseView } from './views/base-view';
export interface EditorCtorProps {
workspace: string;
views: Partial<Record<keyof BlockFlavors, BaseView>>;
plugins: PluginCreator[];
rootBlockId: string;
isWhiteboard?: boolean;
isEdgeless?: boolean;
}
export class Editor implements Virgo {
private _cacheManager = new LRUCache<string, Promise<AsyncBlock | null>>({
max: 8192,
ttl: 1000 * 60 * 30,
});
private _cacheManager = new Map<string, Promise<AsyncBlock | null>>();
public mouseManager = new MouseManager(this);
public selectionManager = new SelectionManager(this);
public keyboardManager = new KeyboardManager(this);
@ -75,7 +71,7 @@ export class Editor implements Virgo {
render: PatchNode;
has: (key: string) => boolean;
};
public isWhiteboard = false;
public isEdgeless = false;
private _isDisposed = false;
constructor(props: EditorCtorProps) {
@ -85,8 +81,8 @@ export class Editor implements Virgo {
this.hooks = new Hooks();
this.plugin_manager = new PluginManager(this, this.hooks);
this.plugin_manager.registerAll(props.plugins);
if (props.isWhiteboard) {
this.isWhiteboard = true;
if (props.isEdgeless) {
this.isEdgeless = true;
}
for (const [name, block] of Object.entries(props.views)) {
services.api.editorBlock.registerContentExporter(
@ -148,18 +144,41 @@ export class Editor implements Virgo {
public get container() {
return this.ui_container;
}
// preference to use withSuspend
/**
* Use it discreetly.
* Preference to use {@link withBatch}
*/
public suspend(flag: boolean) {
services.api.editorBlock.suspend(this.workspace, flag);
}
public async withSuspend<T extends (...args: any[]) => any>(
// TODO support suspend recursion
private _isSuspend = false;
public withBatch<T extends (...args: any[]) => Promise<any>>(fn: T): T {
return (async (...args) => {
if (this._isSuspend) {
console.warn(
'The editor currently has suspend! Please do not call batch method repeatedly!'
);
}
this._isSuspend = true;
services.api.editorBlock.suspend(this.workspace, true);
const result = await fn(...args);
services.api.editorBlock.suspend(this.workspace, false);
this._isSuspend = false;
return result;
}) as T;
}
/**
* Use it discreetly.
* Preference to use {@link withBatch}
*/
public async batch<T extends (...args: any[]) => any>(
fn: T
): Promise<Awaited<ReturnType<T>>> {
services.api.editorBlock.suspend(this.workspace, true);
const result = await fn();
services.api.editorBlock.suspend(this.workspace, false);
return result;
return this.withBatch(fn)();
}
public setReactRenderRoot(props: {
@ -211,7 +230,12 @@ export class Editor implements Virgo {
return await services.api.editorBlock.update(patches);
},
remove: async ({ workspace, id }: WorkspaceAndBlockId) => {
return await services.api.editorBlock.delete({ workspace, id });
const ret = await services.api.editorBlock.delete({
workspace,
id,
});
this._cacheManager.delete(id);
return ret;
},
observe: async (
{ workspace, id }: WorkspaceAndBlockId,

View File

@ -1,15 +1,16 @@
/* eslint-disable max-lines */
import {
debounce,
domToRect,
getBlockIdByDom,
last,
Point,
Rect,
last,
without,
debounce,
getBlockIdByDom,
} from '@toeverything/utils';
import EventEmitter from 'eventemitter3';
import { Protocol } from '@toeverything/datasource/db-service';
import { BlockEditor } from '../..';
import { AsyncBlock } from '../block';
import { VirgoSelection } from '../types';
@ -18,19 +19,17 @@ import {
changeEventName,
CursorTypes,
IdList,
SelectBlock,
selectEndEventName,
SelectEventCallbackTypes,
SelectEventTypes,
SelectInfo,
SelectionSettings,
SelectionSettingsMap,
SelectionTypes,
SelectPosition,
SelectBlock,
SelectInfo,
} from './types';
import { isLikeBlockListIds } from './utils';
import { Protocol } from '@toeverything/datasource/db-service';
import { Editor } from 'slate';
// IMP: maybe merge active and select into single function
export type SelectionInfo = InstanceType<
@ -336,12 +335,12 @@ export class SelectionManager implements VirgoSelection {
});
for await (const childBlock of selectableChildren) {
const { dom } = childBlock;
if (dom && selectionRect.isIntersect(domToRect(dom))) {
selectedNodes.push(childBlock);
}
if (!dom) {
console.warn('can not find dom bind with block');
}
if (dom && selectionRect.isIntersect(domToRect(dom))) {
selectedNodes.push(childBlock);
}
}
// if just only has one selected maybe select the children
if (selectedNodes.length === 1) {
@ -1063,10 +1062,10 @@ export class SelectionManager implements VirgoSelection {
index: number,
blockId: string
): Promise<void> {
let preRang = document.createRange();
const preRang = document.createRange();
preRang.setStart(nowRange.startContainer, index);
preRang.setEnd(nowRange.endContainer, index);
let prePosition = preRang.getClientRects().item(0);
const prePosition = preRang.getClientRects().item(0);
this.activeNodeByNodeId(
blockId,
new Point(prePosition.left, prePosition.bottom)

View File

@ -107,7 +107,7 @@ export interface Virgo {
getBlockDomById: (id: string) => Promise<HTMLElement>;
getBlockByPoint: (point: Point) => Promise<AsyncBlock>;
getGroupBlockByPoint: (point: Point) => Promise<AsyncBlock>;
isWhiteboard: boolean;
isEdgeless: boolean;
mouseManager: MouseManager;
}

View File

@ -9,10 +9,10 @@ import {
BlockDecoration,
MapOperation,
} from '@toeverything/datasource/jwt';
import { cloneDeep } from '@toeverything/utils';
import type { EventData } from '../block';
import { AsyncBlock } from '../block';
import type { Editor } from '../editor';
import { cloneDeep } from '@toeverything/utils';
import { SelectBlock } from '../selection';
export interface CreateView {
@ -114,7 +114,21 @@ export abstract class BaseView {
// Whether the component is empty
isEmpty(block: AsyncBlock): boolean {
const text = block.getProperty('text');
return !text?.value?.[0]?.text;
const result = !text?.value?.[0]?.text;
// Assert that the text is really empty
if (
result &&
block.getProperty('text')?.value.some(content => content.text)
) {
console.warn(
'Assertion isEmpty error! The block has an empty start fragment, but it is not empty',
block
);
}
// Assert end
return result;
}
getSelProperties(block: AsyncBlock, selectInfo: any): DefaultColumnsValue {

View File

@ -7,7 +7,6 @@ export enum RecastScene {
Page = 'page',
Kanban = 'kanban',
Table = 'table',
// Whiteboard = 'whiteboard',
}
export type RecastViewId = string & {

View File

@ -1,4 +1,6 @@
import { MuiClickAwayListener } from '@toeverything/components/ui';
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
import { HookType, PluginHooks, Virgo } from '@toeverything/framework/virgo';
import React, {
useCallback,
useEffect,
@ -6,18 +8,15 @@ import React, {
useRef,
useState,
} from 'react';
import { MuiClickAwayListener } from '@toeverything/components/ui';
import { Virgo, HookType, PluginHooks } from '@toeverything/framework/virgo';
import { CommandMenuContainer } from './Container';
import { QueryResult } from '../../search';
import {
CommandMenuCategories,
commandMenuHandlerMap,
commonCommandMenuHandler,
menuItemsMap,
} from './config';
import { QueryResult } from '../../search';
import { CommandMenuContainer } from './Container';
export type CommandMenuProps = {
editor: Virgo;
hooks: PluginHooks;
@ -225,7 +224,11 @@ export const CommandMenu = ({ editor, hooks, style }: CommandMenuProps) => {
await commonCommandMenuHandler(blockId, type, editor);
}
const block = await editor.getBlockById(blockId);
block.remove();
let nextBlock = await block.nextSibling();
editor.selectionManager.activeNodeByNodeId(nextBlock.id);
if (block.blockProvider.isEmpty()) {
block.remove();
}
} else {
if (Protocol.Block.Type[type as BlockFlavorKeys]) {
const block = await editor.commands.blockCommands.convertBlock(

View File

@ -75,21 +75,6 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
[editor, groupBlock]
);
const handleRootDrop = useCallback(
async (e: React.DragEvent<Element>) => {
let groupBlockOnDrop = null;
if (editor.dragDropManager.isDragGroup(e)) {
groupBlockOnDrop = await editor.getGroupBlockByPoint(
new Point(e.clientX, e.clientY)
);
if (groupBlockOnDrop?.id === groupBlock?.id) {
groupBlockOnDrop = null;
}
}
},
[editor, groupBlock]
);
const handleRootMouseLeave = useCallback(() => setGroupBlock(null), []);
const handleRootDragEnd = () => {
@ -128,7 +113,6 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
handleRootMouseMove,
handleRootMouseDown,
handleRootDragOver,
handleRootDrop,
handleRootMouseLeave,
]);
@ -170,7 +154,11 @@ export const GroupMenu = function ({ editor, hooks }: GroupMenuProps) {
setShowMenu(false);
if (groupBlock) {
const unobserve = groupBlock.onUpdate(() => setGroupBlock(null));
const unobserve = groupBlock.onUpdate(() => {
requestAnimationFrame(() => {
setGroupBlock(null);
});
});
return unobserve;
}
return undefined;

View File

@ -14,7 +14,7 @@ export class GroupMenuPlugin extends BasePlugin {
}
protected override _onRender(): void {
if (this.editor.isWhiteboard) return;
if (this.editor.isEdgeless) return;
this.root = new PluginRenderRoot({
name: PLUGIN_NAME,
render: this.editor.reactRenderRoot.render,

View File

@ -1,47 +1,47 @@
/* eslint-disable max-lines */
import {
HeadingOneIcon,
HeadingTwoIcon,
HeadingThreeIcon,
ToDoIcon,
NumberIcon,
BulletIcon,
FormatBoldEmphasisIcon,
FormatItalicIcon,
FormatStrikethroughIcon,
LinkIcon,
CodeIcon,
FormatColorTextIcon,
FormatBackgroundIcon,
AlignLeftIcon,
AlignCenterIcon,
AlignRightIcon,
TurnIntoIcon,
BacklinksIcon,
MoreIcon,
TextFontIcon,
QuoteIcon,
CalloutIcon,
FileIcon,
ImageIcon,
PagesIcon,
CodeBlockIcon,
CommentIcon,
} from '@toeverything/components/icons';
import {
fontBgColorPalette,
fontColorPalette,
type TextAlignOptions,
} from '@toeverything/components/common';
import { Virgo } from '@toeverything/framework/virgo';
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
import { ClickItemHandler, InlineMenuItem } from './types';
import {
inlineMenuNamesKeys,
inlineMenuNamesForFontColor,
INLINE_MENU_UI_TYPES,
BacklinksIcon,
BulletIcon,
CalloutIcon,
CodeBlockIcon,
CodeIcon,
CommentIcon,
FileIcon,
FormatBackgroundIcon,
FormatBoldEmphasisIcon,
FormatColorTextIcon,
FormatItalicIcon,
FormatStrikethroughIcon,
HeadingOneIcon,
HeadingThreeIcon,
HeadingTwoIcon,
ImageIcon,
LinkIcon,
MoreIcon,
NumberIcon,
PagesIcon,
QuoteIcon,
TextAlignCenterIcon,
TextAlignLeftIcon,
TextAlignRightIcon,
TextFontIcon,
ToDoIcon,
TurnIntoIcon,
} from '@toeverything/components/icons';
import { BlockFlavorKeys, Protocol } from '@toeverything/datasource/db-service';
import { Virgo } from '@toeverything/framework/virgo';
import {
inlineMenuNames,
inlineMenuNamesForFontColor,
inlineMenuNamesKeys,
INLINE_MENU_UI_TYPES,
} from './config';
import { ClickItemHandler, InlineMenuItem } from './types';
const convert_to_block_type = async ({
editor,
@ -732,27 +732,27 @@ export const getInlineMenuData = ({
},
{
type: INLINE_MENU_UI_TYPES.dropdown,
icon: AlignLeftIcon,
icon: TextAlignLeftIcon,
name: inlineMenuNames.currentTextAlign,
nameKey: inlineMenuNamesKeys.currentTextAlign,
children: [
{
type: INLINE_MENU_UI_TYPES.icon,
icon: AlignLeftIcon,
icon: TextAlignLeftIcon,
name: inlineMenuNames.alignLeft,
nameKey: inlineMenuNamesKeys.alignLeft,
onClick: common_handler_for_inline_menu,
},
{
type: INLINE_MENU_UI_TYPES.icon,
icon: AlignCenterIcon,
icon: TextAlignCenterIcon,
name: inlineMenuNames.alignCenter,
nameKey: inlineMenuNamesKeys.alignCenter,
onClick: common_handler_for_inline_menu,
},
{
type: INLINE_MENU_UI_TYPES.icon,
icon: AlignRightIcon,
icon: TextAlignRightIcon,
name: inlineMenuNames.alignRight,
nameKey: inlineMenuNamesKeys.alignRight,
onClick: common_handler_for_inline_menu,

View File

@ -1,15 +1,15 @@
import { HookType, BlockDropPlacement } from '@toeverything/framework/virgo';
import { StrictMode } from 'react';
import { BasePlugin } from '../../base-plugin';
import { ignoreBlockTypes } from './menu-config';
import {
LineInfoSubject,
LeftMenuDraggable,
BlockDomInfo,
} from './LeftMenuDraggable';
import { PluginRenderRoot } from '../../utils';
import { Subject, throttleTime } from 'rxjs';
import { BlockDropPlacement, HookType } from '@toeverything/framework/virgo';
import { domToRect, last, Point } from '@toeverything/utils';
import { StrictMode } from 'react';
import { Subject, throttleTime } from 'rxjs';
import { BasePlugin } from '../../base-plugin';
import { PluginRenderRoot } from '../../utils';
import {
BlockDomInfo,
LeftMenuDraggable,
LineInfoSubject,
} from './LeftMenuDraggable';
import { ignoreBlockTypes } from './menu-config';
const DRAG_THROTTLE_DELAY = 60;
export class LeftMenuPlugin extends BasePlugin {
private _mousedown?: boolean;
@ -111,6 +111,10 @@ export class LeftMenuPlugin extends BasePlugin {
block.dom,
block.id
);
if (!targetBlock.dom) {
console.warn('Can not find dom bind with block', targetBlock);
return;
}
this._lineInfo.next({
direction,
blockInfo: {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M18.4 5.6H5.6v12.8h12.8V5.6ZM4 4v16h16V4H4Z" clip-rule="evenodd"/><path d="M3 3H5V5H3z"/><path d="M3 19H5V21H3z"/><path d="M3 11H5V13H3z"/><path d="M11 3H13V5H11z"/><path d="M11 19H13V21H11z"/><path d="M11 11H13V13H11z"/><path d="M19 3H21V5H19z"/><path d="M19 19H21V21H19z"/><path d="M19 11H21V13H19z"/></svg>

After

Width:  |  Height:  |  Size: 398 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface AlignIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const AlignIcon = ({ color, style, ...props}: AlignIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M18.4 5.6H5.6v12.8h12.8V5.6ZM4 4v16h16V4H4Z" clipRule="evenodd" /><path d="M3 3H5V5H3z" /><path d="M3 19H5V21H3z" /><path d="M3 11H5V13H3z" /><path d="M11 3H13V5H11z" /><path d="M11 19H13V21H11z" /><path d="M11 11H13V13H11z" /><path d="M19 3H21V5H19z" /><path d="M19 19H21V21H19z" /><path d="M19 11H21V13H19z" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M18.566 9.434 12 2.87 5.434 9.434l1.132 1.132L11.2 5.93V21h1.6V5.931l4.634 4.635 1.132-1.132Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 211 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface BringForwardIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const BringForwardIcon = ({ color, style, ...props}: BringForwardIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M18.566 9.434 12 2.87 5.434 9.434l1.132 1.132L11.2 5.93V21h1.6V5.931l4.634 4.635 1.132-1.132Z" clipRule="evenodd" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M18.566 12.434 12 5.87l-6.566 6.565 1.132 1.132L11.2 8.93V21h1.6V8.931l4.634 4.635 1.132-1.132ZM5 3h14v1.6H5V3Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 229 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface BringToFrontIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const BringToFrontIcon = ({ color, style, ...props}: BringToFrontIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M18.566 12.434 12 5.87l-6.566 6.565 1.132 1.132L11.2 8.93V21h1.6V8.931l4.634 4.635 1.132-1.132ZM5 3h14v1.6H5V3Z" clipRule="evenodd" />
</SvgIcon>
)
};

View File

@ -1,4 +1,4 @@
export const timestamp = 1660270988401;
export const timestamp = 1660793621971;
export * from './image/image';
export * from './format-clear/format-clear';
export * from './backward-undo/backward-undo';
@ -26,10 +26,10 @@ export * from './format-italic/format-italic';
export * from './format-strikethrough/format-strikethrough';
export * from './format-color-text/format-color-text';
export * from './format-background/format-background';
export * from './align-left/align-left';
export * from './align-center/align-center';
export * from './align-right/align-right';
export * from './align-justify/align-justify';
export * from './text-align-left/text-align-left';
export * from './text-align-center/text-align-center';
export * from './text-align-right/text-align-right';
export * from './text-align-justify/text-align-justify';
export * from './arrow-drop-down/arrow-drop-down';
export * from './arrow-right/arrow-right';
export * from './more/more';
@ -129,4 +129,18 @@ export * from './unlock/unlock';
export * from './more-tags-an-subblocks/more-tags-an-subblocks';
export * from './page-in-page-tree/page-in-page-tree';
export * from './pin/pin';
export * from './add/add';
export * from './add/add';
export * from './shapes-align-left/shapes-align-left';
export * from './shapes-align-right/shapes-align-right';
export * from './shapes-distribute-horizontal/shapes-distribute-horizontal';
export * from './shapes-distribute-vertical/shapes-distribute-vertical';
export * from './shapes-align-top/shapes-align-top';
export * from './shapes-align-bottom/shapes-align-bottom';
export * from './shapes-align-horizontal-center/shapes-align-horizontal-center';
export * from './shapes-align-vertical-center/shapes-align-vertical-center';
export * from './bring-forward/bring-forward';
export * from './send-backward/send-backward';
export * from './bring-to-front/bring-to-front';
export * from './send-to-back/send-to-back';
export * from './align/align';
export * from './layers/layers';

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M5.578 8 12 11.211 18.422 8 12 4.789 5.578 8Zm5.975-4.776L3.789 7.106a1 1 0 0 0 0 1.788l7.764 3.882a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.788l-7.764-3.882a1 1 0 0 0-.894 0Z" clip-rule="evenodd"/><path fill-rule="evenodd" d="m5.579 12-1.789-.895h-.001a1 1 0 0 0 0 1.79l7.764 3.881a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.788l-.001-.001-1.789.894.001.001L12 15.211 5.578 12Z" clip-rule="evenodd"/><path fill-rule="evenodd" d="M5.579 16.094 3.79 15.2h-.001a1 1 0 0 0 0 1.79l7.764 3.881a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.789h-.001l-1.789.894.001.001L12 19.306l-6.422-3.211Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 704 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface LayersIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const LayersIcon = ({ color, style, ...props}: LayersIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M5.578 8 12 11.211 18.422 8 12 4.789 5.578 8Zm5.975-4.776L3.789 7.106a1 1 0 0 0 0 1.788l7.764 3.882a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.788l-7.764-3.882a1 1 0 0 0-.894 0Z" clipRule="evenodd" /><path fillRule="evenodd" d="m5.579 12-1.789-.895h-.001a1 1 0 0 0 0 1.79l7.764 3.881a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.788l-.001-.001-1.789.894.001.001L12 15.211 5.578 12Z" clipRule="evenodd" /><path fillRule="evenodd" d="M5.579 16.094 3.79 15.2h-.001a1 1 0 0 0 0 1.79l7.764 3.881a1 1 0 0 0 .894 0l7.764-3.882a1 1 0 0 0 0-1.789h-.001l-1.789.894.001.001L12 19.306l-6.422-3.211Z" clipRule="evenodd" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12.8 18.069V3h-1.6v15.069l-4.634-4.635-1.132 1.132L12 21.13l6.566-6.565-1.132-1.132L12.8 18.07Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 214 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface SendBackwardIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const SendBackwardIcon = ({ color, style, ...props}: SendBackwardIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M12.8 18.069V3h-1.6v15.069l-4.634-4.635-1.132 1.132L12 21.13l6.566-6.565-1.132-1.132L12.8 18.07Z" clipRule="evenodd" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12.8 15.069V3h-1.6v12.069l-4.634-4.635-1.132 1.132L12 18.13l6.566-6.565-1.132-1.132L12.8 15.07ZM19 21H5v-1.6h14V21Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 234 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface SendToBackIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const SendToBackIcon = ({ color, style, ...props}: SendToBackIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M12.8 15.069V3h-1.6v12.069l-4.634-4.635-1.132 1.132L12 18.13l6.566-6.565-1.132-1.132L12.8 15.07ZM19 21H5v-1.6h14V21Z" clipRule="evenodd" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M21 21H3v-1h18v1Z" clip-rule="evenodd"/><path d="M7 17H15V20H7z" transform="rotate(-90 7 17)"/><path d="M14 17H28V20H14z" transform="rotate(-90 14 17)"/></svg>

After

Width:  |  Height:  |  Size: 248 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignBottomIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignBottomIcon = ({ color, style, ...props}: ShapesAlignBottomIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M21 21H3v-1h18v1Z" clipRule="evenodd" /><path d="M7 17H15V20H7z" transform="rotate(-90 7 17)" /><path d="M14 17H28V20H14z" transform="rotate(-90 14 17)" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M11.5 21V3h1v18h-1Z" clip-rule="evenodd"/><path d="M8 7H16V10H8z"/><path d="M5 14H19V17H5z"/></svg>

After

Width:  |  Height:  |  Size: 188 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignHorizontalCenterIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignHorizontalCenterIcon = ({ color, style, ...props}: ShapesAlignHorizontalCenterIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M11.5 21V3h1v18h-1Z" clipRule="evenodd" /><path d="M8 7H16V10H8z" /><path d="M5 14H19V17H5z" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M3 21V3h1v18H3Z" clip-rule="evenodd"/><path d="M7 7H15V10H7z"/><path d="M7 14H21V17H7z"/></svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignLeftIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignLeftIcon = ({ color, style, ...props}: ShapesAlignLeftIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M3 21V3h1v18H3Z" clipRule="evenodd" /><path d="M7 7H15V10H7z" /><path d="M7 14H21V17H7z" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M21 21V3h-1v18h1Z" clip-rule="evenodd"/><path d="M0 0H8V3H0z" transform="matrix(-1 0 0 1 17 7)"/><path d="M0 0H14V3H0z" transform="matrix(-1 0 0 1 17 14)"/></svg>

After

Width:  |  Height:  |  Size: 251 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignRightIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignRightIcon = ({ color, style, ...props}: ShapesAlignRightIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M21 21V3h-1v18h1Z" clipRule="evenodd" /><path d="M0 0H8V3H0z" transform="matrix(-1 0 0 1 17 7)" /><path d="M0 0H14V3H0z" transform="matrix(-1 0 0 1 17 14)" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M21 3H3v1h18V3Z" clip-rule="evenodd"/><path d="M0 0H8V3H0z" transform="matrix(0 1 1 0 7 7)"/><path d="M0 0H14V3H0z" transform="matrix(0 1 1 0 14 7)"/></svg>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignTopIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignTopIcon = ({ color, style, ...props}: ShapesAlignTopIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M21 3H3v1h18V3Z" clipRule="evenodd" /><path d="M0 0H8V3H0z" transform="matrix(0 1 1 0 7 7)" /><path d="M0 0H14V3H0z" transform="matrix(0 1 1 0 14 7)" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M21 12.5H3v-1h18v1Z" clip-rule="evenodd"/><path d="M7 16H15V19H7z" transform="rotate(-90 7 16)"/><path d="M14 19H28V22H14z" transform="rotate(-90 14 19)"/></svg>

After

Width:  |  Height:  |  Size: 250 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesAlignVerticalCenterIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesAlignVerticalCenterIcon = ({ color, style, ...props}: ShapesAlignVerticalCenterIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M21 12.5H3v-1h18v1Z" clipRule="evenodd" /><path d="M7 16H15V19H7z" transform="rotate(-90 7 16)" /><path d="M14 19H28V22H14z" transform="rotate(-90 14 19)" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M20 21V3h-1v18h1ZM5 21V3H4v18h1Z" clip-rule="evenodd"/><path d="M0 0H10V3H0z" transform="matrix(0 -1 -1 0 13.5 17)"/></svg>

After

Width:  |  Height:  |  Size: 212 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesDistributeHorizontalIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesDistributeHorizontalIcon = ({ color, style, ...props}: ShapesDistributeHorizontalIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M20 21V3h-1v18h1ZM5 21V3H4v18h1Z" clipRule="evenodd" /><path d="M0 0H10V3H0z" transform="matrix(0 -1 -1 0 13.5 17)" />
</SvgIcon>
)
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M20.75 4.25h-18v1h18v-1ZM20.75 18.75h-18v1h18v-1Z" clip-rule="evenodd"/><path d="M0 0H10V3H0z" transform="matrix(-1 0 0 1 17 10.5)"/></svg>

After

Width:  |  Height:  |  Size: 228 B

View File

@ -0,0 +1,18 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface ShapesDistributeVerticalIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const ShapesDistributeVerticalIcon = ({ color, style, ...props}: ShapesDistributeVerticalIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}
return (
<SvgIcon style={styles} {...props}>
<path fillRule="evenodd" d="M20.75 4.25h-18v1h18v-1ZM20.75 18.75h-18v1h18v-1Z" clipRule="evenodd" /><path d="M0 0H10V3H0z" transform="matrix(-1 0 0 1 17 10.5)" />
</SvgIcon>
)
};

View File

@ -2,11 +2,11 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface AlignCenterIconProps extends Omit<SvgIconProps, 'color'> {
export interface TextAlignCenterIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const AlignCenterIcon = ({ color, style, ...props}: AlignCenterIconProps) => {
export const TextAlignCenterIcon = ({ color, style, ...props}: TextAlignCenterIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}

View File

@ -2,11 +2,11 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface AlignJustifyIconProps extends Omit<SvgIconProps, 'color'> {
export interface TextAlignJustifyIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const AlignJustifyIcon = ({ color, style, ...props}: AlignJustifyIconProps) => {
export const TextAlignJustifyIcon = ({ color, style, ...props}: TextAlignJustifyIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}

View File

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 190 B

View File

@ -2,11 +2,11 @@
// eslint-disable-next-line no-restricted-imports
import { SvgIcon, SvgIconProps } from '@mui/material';
export interface AlignLeftIconProps extends Omit<SvgIconProps, 'color'> {
export interface TextAlignLeftIconProps extends Omit<SvgIconProps, 'color'> {
color?: string
}
export const AlignLeftIcon = ({ color, style, ...props}: AlignLeftIconProps) => {
export const TextAlignLeftIcon = ({ color, style, ...props}: TextAlignLeftIconProps) => {
const propsStyles = {"color": color};
const customStyles = {};
const styles = {...propsStyles, ...customStyles, ...style}

Some files were not shown because too many files have changed in this diff Show More