Merge branch 'develop' into fix/experience
@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ module.exports = {
|
||||
],
|
||||
scopes: [
|
||||
{ name: 'selection' },
|
||||
{ name: 'whiteboard' },
|
||||
{ name: 'edgeless' },
|
||||
{ name: 'point' },
|
||||
{ name: 'group' },
|
||||
{ name: 'page' },
|
||||
|
1
.env
@ -1,4 +1,5 @@
|
||||
# use for download icon from figma
|
||||
|
||||
FIGMA_TOKEN
|
||||
NODE_ENV
|
||||
AFFINE_FEATURE_FLAG_TOKEN
|
||||
|
1
.vscode/settings.json
vendored
@ -25,6 +25,7 @@
|
||||
"Kanban",
|
||||
"keyval",
|
||||
"ligo",
|
||||
"livedemo",
|
||||
"lozad",
|
||||
"mastersthesis",
|
||||
"nrwl",
|
||||
|
@ -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>
|
||||
|
||||
|
11
apps/ligo-virgo-e2e/package.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
||||
|
@ -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) => { ... })
|
||||
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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',
|
||||
],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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',
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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**!
|
@ -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_ | _tsconfig.xx_.json | xx.spec .ts | .env.xx | 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() |
|
@ -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)
|
@ -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)
|
@ -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';
|
||||
```
|
@ -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.
|
@ -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`
|
@ -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';
|
||||
```
|
@ -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(() => {
|
||||
|
@ -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;
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
},
|
||||
})
|
||||
);
|
@ -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>
|
||||
);
|
||||
};
|
@ -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);
|
||||
|
@ -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">
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -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,
|
||||
},
|
||||
{
|
||||
|
@ -111,7 +111,8 @@ export class TranslateSession extends BaseSession {
|
||||
Utils.boundsContain(
|
||||
TLDR.get_bounds(shap),
|
||||
TLDR.get_bounds(shapItem)
|
||||
)
|
||||
) &&
|
||||
!shapItem.isLocked
|
||||
) {
|
||||
selectedShapes.push(shapItem);
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
1
libs/components/board-tools/src/hand-drag/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './hand-drag-tool';
|
@ -1 +0,0 @@
|
||||
export * from './hand-draw-tool';
|
@ -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,
|
||||
};
|
||||
|
@ -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',
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -173,7 +173,7 @@ export const BulletView = ({ block, editor }: CreateView) => {
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return tabBlock(block, isShiftKey);
|
||||
return tabBlock(editor, block, isShiftKey);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -103,7 +103,7 @@ export const GroupView = (props: CreateView) => {
|
||||
<View {...props} />
|
||||
</GroupContainer>
|
||||
|
||||
{editor.isWhiteboard ? null : (
|
||||
{editor.isEdgeless ? null : (
|
||||
<GroupAction onAddGroup={addGroup} />
|
||||
)}
|
||||
</GroupBox>
|
||||
|
@ -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 */
|
||||
|
@ -167,7 +167,7 @@ export const NumberedView = ({ block, editor }: CreateView) => {
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
tabBlock(block, isShiftKey);
|
||||
tabBlock(editor, block, isShiftKey);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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];
|
||||
};
|
||||
|
@ -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=)([^#&?]*).*/;
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -7,7 +7,6 @@ export enum RecastScene {
|
||||
Page = 'page',
|
||||
Kanban = 'kanban',
|
||||
Table = 'table',
|
||||
// Whiteboard = 'whiteboard',
|
||||
}
|
||||
|
||||
export type RecastViewId = string & {
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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: {
|
||||
|
1
libs/components/icons/src/auto-icons/align/align.svg
Normal 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 |
18
libs/components/icons/src/auto-icons/align/align.tsx
Normal 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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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';
|
1
libs/components/icons/src/auto-icons/layers/layers.svg
Normal 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 |
18
libs/components/icons/src/auto-icons/layers/layers.tsx
Normal 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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
@ -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 |
@ -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>
|
||||
)
|
||||
};
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |
@ -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}
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |
@ -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}
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |
@ -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}
|
Before Width: | Height: | Size: 190 B After Width: | Height: | Size: 190 B |